auditable 0.2.1__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 (208) hide show
  1. auditable-0.4.0/CHANGELOG.md +42 -0
  2. auditable-0.4.0/PKG-INFO +452 -0
  3. auditable-0.4.0/README.md +382 -0
  4. {auditable-0.2.1 → auditable-0.4.0}/examples/README.md +283 -228
  5. auditable-0.4.0/examples/example_add_your_method.py +68 -0
  6. auditable-0.4.0/examples/example_agentpile.py +71 -0
  7. {auditable-0.2.1 → auditable-0.4.0}/examples/example_langchain_capture.py +96 -96
  8. {auditable-0.2.1 → auditable-0.4.0}/examples/example_langgraph_capture.py +101 -101
  9. auditable-0.4.0/examples/example_leaderboard.py +41 -0
  10. {auditable-0.2.1 → auditable-0.4.0}/examples/example_mcp_capture.py +116 -116
  11. auditable-0.4.0/examples/example_methods.py +62 -0
  12. {auditable-0.2.1 → auditable-0.4.0}/examples/example_payment_audit.py +113 -113
  13. {auditable-0.2.1 → auditable-0.4.0}/examples/example_post_rank_run.py +80 -80
  14. {auditable-0.2.1 → auditable-0.4.0}/examples/example_state_anomaly.py +119 -119
  15. auditable-0.4.0/examples/leaderboard.ipynb +275 -0
  16. {auditable-0.2.1 → auditable-0.4.0}/pyproject.toml +103 -79
  17. {auditable-0.2.1 → auditable-0.4.0}/src/auditable/__init__.py +96 -79
  18. {auditable-0.2.1 → auditable-0.4.0}/src/auditable/analysis.py +615 -615
  19. {auditable-0.2.1 → auditable-0.4.0}/src/auditable/audit_report.py +1055 -1055
  20. auditable-0.4.0/src/auditable/base.py +272 -0
  21. auditable-0.4.0/src/auditable/bench/__init__.py +16 -0
  22. auditable-0.4.0/src/auditable/bench/__main__.py +66 -0
  23. auditable-0.4.0/src/auditable/bench/_core.py +417 -0
  24. auditable-0.4.0/src/auditable/bench/corpora/__init__.py +8 -0
  25. auditable-0.4.0/src/auditable/bench/corpora/pre.py +73 -0
  26. auditable-0.4.0/src/auditable/bench/corpora/swegym.py +55 -0
  27. auditable-0.4.0/src/auditable/bench/corpora/tau.py +92 -0
  28. auditable-0.4.0/src/auditable/bench/corpora/whoandwhen.py +94 -0
  29. auditable-0.4.0/src/auditable/bench/data/llm_judge/README.md +44 -0
  30. auditable-0.4.0/src/auditable/bench/data/llm_judge/legacy_run_keys.json +131 -0
  31. auditable-0.4.0/src/auditable/bench/data/llm_judge/whoandwhen__all_at_once__claude-opus-4.8.json +1869 -0
  32. auditable-0.4.0/src/auditable/bench/data/llm_judge/whoandwhen__all_at_once__deepseek-r1.json +1839 -0
  33. auditable-0.4.0/src/auditable/bench/data/llm_judge/whoandwhen__all_at_once__gemini.json +1723 -0
  34. auditable-0.4.0/src/auditable/bench/data/llm_judge/whoandwhen__all_at_once__gemma-3-12b.json +1758 -0
  35. auditable-0.4.0/src/auditable/bench/data/llm_judge/whoandwhen__all_at_once__gpt-5.4.json +1869 -0
  36. auditable-0.4.0/src/auditable/bench/data/llm_judge/whoandwhen__all_at_once__gpt-5.5.json +1869 -0
  37. auditable-0.4.0/src/auditable/bench/data/llm_judge/whoandwhen__all_at_once__gpt-oss-20b.json +1741 -0
  38. auditable-0.4.0/src/auditable/bench/data/llm_judge/whoandwhen__all_at_once__llama-3.3-70b.json +1869 -0
  39. auditable-0.4.0/src/auditable/bench/data/llm_judge/whoandwhen__all_at_once__mistral-small.json +1602 -0
  40. auditable-0.4.0/src/auditable/bench/data/llm_judge/whoandwhen__all_at_once__nova-micro.json +1439 -0
  41. auditable-0.4.0/src/auditable/bench/data/llm_judge/whoandwhen__all_at_once__qwen3-32b.json +1854 -0
  42. auditable-0.4.0/src/auditable/bench/data/llm_judge/whoandwhen__binary_search__claude-opus-4.8.json +896 -0
  43. auditable-0.4.0/src/auditable/bench/data/llm_judge/whoandwhen__binary_search__deepseek-r1.json +896 -0
  44. auditable-0.4.0/src/auditable/bench/data/llm_judge/whoandwhen__binary_search__gemini.json +896 -0
  45. auditable-0.4.0/src/auditable/bench/data/llm_judge/whoandwhen__binary_search__gemma-3-12b.json +896 -0
  46. auditable-0.4.0/src/auditable/bench/data/llm_judge/whoandwhen__binary_search__gpt-5.4.json +896 -0
  47. auditable-0.4.0/src/auditable/bench/data/llm_judge/whoandwhen__binary_search__gpt-5.5.json +896 -0
  48. auditable-0.4.0/src/auditable/bench/data/llm_judge/whoandwhen__binary_search__llama-3.3-70b.json +896 -0
  49. auditable-0.4.0/src/auditable/bench/data/llm_judge/whoandwhen__binary_search__mistral-small.json +896 -0
  50. auditable-0.4.0/src/auditable/bench/data/llm_judge/whoandwhen__binary_search__nova-micro.json +896 -0
  51. auditable-0.4.0/src/auditable/bench/data/llm_judge/whoandwhen__binary_search__qwen3-32b.json +896 -0
  52. auditable-0.4.0/src/auditable/bench/data/llm_judge/whoandwhen__step_by_step__claude-opus-4.8.json +896 -0
  53. auditable-0.4.0/src/auditable/bench/data/llm_judge/whoandwhen__step_by_step__deepseek-r1.json +896 -0
  54. auditable-0.4.0/src/auditable/bench/data/llm_judge/whoandwhen__step_by_step__gemini.json +896 -0
  55. auditable-0.4.0/src/auditable/bench/data/llm_judge/whoandwhen__step_by_step__gemma-3-12b.json +896 -0
  56. auditable-0.4.0/src/auditable/bench/data/llm_judge/whoandwhen__step_by_step__gpt-5.4.json +896 -0
  57. auditable-0.4.0/src/auditable/bench/data/llm_judge/whoandwhen__step_by_step__gpt-5.5.json +896 -0
  58. auditable-0.4.0/src/auditable/bench/data/llm_judge/whoandwhen__step_by_step__llama-3.3-70b.json +896 -0
  59. auditable-0.4.0/src/auditable/bench/data/llm_judge/whoandwhen__step_by_step__mistral-small.json +896 -0
  60. auditable-0.4.0/src/auditable/bench/data/llm_judge/whoandwhen__step_by_step__nova-micro.json +896 -0
  61. auditable-0.4.0/src/auditable/bench/data/llm_judge/whoandwhen__step_by_step__qwen3-32b.json +896 -0
  62. auditable-0.4.0/src/auditable/bench/data/pre/README.md +17 -0
  63. auditable-0.4.0/src/auditable/bench/data/pre/llm_judge_method/llama-3.3-70b.json +4757 -0
  64. auditable-0.4.0/src/auditable/bench/golden/detect__swegym.json +100 -0
  65. auditable-0.4.0/src/auditable/bench/golden/detect__tau.json +105 -0
  66. auditable-0.4.0/src/auditable/bench/golden/live__swegym.json +80 -0
  67. auditable-0.4.0/src/auditable/bench/golden/live__tau.json +80 -0
  68. auditable-0.4.0/src/auditable/bench/golden/localize__whoandwhen.json +42 -0
  69. auditable-0.4.0/src/auditable/bench/golden/localize__whoandwhen__llm.json +197 -0
  70. auditable-0.4.0/src/auditable/bench/golden/pre__over_privilege.json +157 -0
  71. auditable-0.4.0/src/auditable/bench/llm_rows.py +421 -0
  72. auditable-0.4.0/src/auditable/bench/protocols.py +118 -0
  73. auditable-0.4.0/src/auditable/bench/protocols_config.py +76 -0
  74. auditable-0.4.0/src/auditable/bench/protocols_live.py +109 -0
  75. auditable-0.4.0/src/auditable/bench/protocols_step.py +133 -0
  76. auditable-0.4.0/src/auditable/bench/tasks/__init__.py +5 -0
  77. auditable-0.4.0/src/auditable/bench/tasks/detect.py +64 -0
  78. auditable-0.4.0/src/auditable/bench/tasks/live.py +112 -0
  79. auditable-0.4.0/src/auditable/bench/tasks/localize.py +112 -0
  80. auditable-0.4.0/src/auditable/bench/tasks/pre.py +219 -0
  81. {auditable-0.2.1 → auditable-0.4.0}/src/auditable/chain.py +1 -1
  82. {auditable-0.2.1 → auditable-0.4.0}/src/auditable/data/__init__.py +260 -256
  83. {auditable-0.2.1 → auditable-0.4.0}/src/auditable/data/inject.py +225 -225
  84. auditable-0.4.0/src/auditable/datasets.py +96 -0
  85. auditable-0.4.0/src/auditable/featurize/__init__.py +72 -0
  86. auditable-0.4.0/src/auditable/featurize/_grade_projection.py +307 -0
  87. auditable-0.4.0/src/auditable/featurize/agentpile.py +497 -0
  88. auditable-0.4.0/src/auditable/featurize/pyg.py +43 -0
  89. auditable-0.4.0/src/auditable/featurize/spec.py +372 -0
  90. auditable-0.4.0/src/auditable/featurize/step_deps.py +97 -0
  91. auditable-0.4.0/src/auditable/featurize/trace.py +163 -0
  92. auditable-0.4.0/src/auditable/featurize/who_when.py +71 -0
  93. {auditable-0.2.1 → auditable-0.4.0}/src/auditable/graph/__init__.py +3 -0
  94. {auditable-0.2.1 → auditable-0.4.0}/src/auditable/graph/adapters/__init__.py +44 -44
  95. {auditable-0.2.1 → auditable-0.4.0}/src/auditable/graph/adapters/own_record.py +142 -142
  96. {auditable-0.2.1 → auditable-0.4.0}/src/auditable/graph/adapters/protocol.py +64 -64
  97. {auditable-0.2.1 → auditable-0.4.0}/src/auditable/graph/adapters/tau_bench.py +138 -138
  98. {auditable-0.2.1 → auditable-0.4.0}/src/auditable/graph/features.py +56 -14
  99. {auditable-0.2.1 → auditable-0.4.0}/src/auditable/graph/grounding.py +332 -332
  100. {auditable-0.2.1 → auditable-0.4.0}/src/auditable/graph/loader.py +250 -250
  101. {auditable-0.2.1 → auditable-0.4.0}/src/auditable/graph/risk.py +130 -128
  102. {auditable-0.2.1 → auditable-0.4.0}/src/auditable/graph/session.py +292 -292
  103. {auditable-0.2.1 → auditable-0.4.0}/src/auditable/harness/__init__.py +7 -0
  104. {auditable-0.2.1 → auditable-0.4.0}/src/auditable/integrations/__init__.py +10 -10
  105. {auditable-0.2.1 → auditable-0.4.0}/src/auditable/integrations/langchain.py +805 -805
  106. {auditable-0.2.1 → auditable-0.4.0}/src/auditable/integrations/mcp.py +328 -328
  107. auditable-0.4.0/src/auditable/llm/__init__.py +7 -0
  108. auditable-0.4.0/src/auditable/llm/backend.py +87 -0
  109. auditable-0.4.0/src/auditable/llm/cache.py +63 -0
  110. auditable-0.4.0/src/auditable/llm/who_when.py +217 -0
  111. auditable-0.4.0/src/auditable/methods/__init__.py +134 -0
  112. auditable-0.4.0/src/auditable/methods/_pygod.py +101 -0
  113. auditable-0.4.0/src/auditable/methods/config/__init__.py +1 -0
  114. auditable-0.4.0/src/auditable/methods/config/llm.py +227 -0
  115. auditable-0.4.0/src/auditable/methods/config/rules.py +244 -0
  116. auditable-0.4.0/src/auditable/methods/online/__init__.py +1 -0
  117. auditable-0.4.0/src/auditable/methods/online/rules.py +112 -0
  118. auditable-0.4.0/src/auditable/methods/run/__init__.py +1 -0
  119. auditable-0.4.0/src/auditable/methods/run/baselines.py +64 -0
  120. auditable-0.4.0/src/auditable/methods/run/graph.py +410 -0
  121. auditable-0.4.0/src/auditable/methods/run/pyod.py +166 -0
  122. auditable-0.4.0/src/auditable/methods/run/supervised.py +172 -0
  123. auditable-0.4.0/src/auditable/methods/step/__init__.py +1 -0
  124. auditable-0.4.0/src/auditable/methods/step/graph.py +99 -0
  125. auditable-0.4.0/src/auditable/methods/step/llm.py +170 -0
  126. auditable-0.4.0/src/auditable/methods/step/rules.py +219 -0
  127. auditable-0.4.0/src/auditable/methods/step/supervised.py +139 -0
  128. {auditable-0.2.1 → auditable-0.4.0}/src/auditable/record.py +5 -3
  129. auditable-0.4.0/tests/fixtures/agentpile_grade/README.md +3 -0
  130. auditable-0.4.0/tests/fixtures/agentpile_grade/agent_reward_bench_records.json +204 -0
  131. auditable-0.4.0/tests/fixtures/agentpile_grade/b_openhands_multi_call_turn.json +36 -0
  132. auditable-0.4.0/tests/fixtures/agentpile_grade/swe_agent_synthetic.json +376 -0
  133. auditable-0.4.0/tests/fixtures/grade_3839a57_features.json +1 -0
  134. {auditable-0.2.1 → auditable-0.4.0}/tests/test_adapters.py +352 -352
  135. auditable-0.4.0/tests/test_agentpile_adapter.py +540 -0
  136. {auditable-0.2.1 → auditable-0.4.0}/tests/test_analysis.py +188 -188
  137. auditable-0.4.0/tests/test_bench_core.py +284 -0
  138. auditable-0.4.0/tests/test_bench_extras.py +25 -0
  139. auditable-0.4.0/tests/test_bench_golden_coverage.py +174 -0
  140. auditable-0.4.0/tests/test_bench_live.py +137 -0
  141. auditable-0.4.0/tests/test_bench_llm_rows.py +203 -0
  142. auditable-0.4.0/tests/test_bench_localize.py +185 -0
  143. auditable-0.4.0/tests/test_bench_pre.py +325 -0
  144. auditable-0.4.0/tests/test_bench_protocols.py +203 -0
  145. auditable-0.4.0/tests/test_bench_swegym_selection.py +132 -0
  146. auditable-0.4.0/tests/test_bench_tau.py +109 -0
  147. {auditable-0.2.1 → auditable-0.4.0}/tests/test_data_inject.py +194 -194
  148. {auditable-0.2.1 → auditable-0.4.0}/tests/test_data_module.py +144 -144
  149. auditable-0.4.0/tests/test_datasets.py +48 -0
  150. auditable-0.4.0/tests/test_featurize.py +186 -0
  151. {auditable-0.2.1 → auditable-0.4.0}/tests/test_graph_features.py +3 -1
  152. {auditable-0.2.1 → auditable-0.4.0}/tests/test_graph_loader.py +293 -293
  153. {auditable-0.2.1 → auditable-0.4.0}/tests/test_graph_risk.py +134 -134
  154. {auditable-0.2.1 → auditable-0.4.0}/tests/test_grounding.py +249 -249
  155. {auditable-0.2.1 → auditable-0.4.0}/tests/test_langchain_integration.py +292 -292
  156. {auditable-0.2.1 → auditable-0.4.0}/tests/test_live_session.py +372 -372
  157. auditable-0.4.0/tests/test_llm.py +207 -0
  158. {auditable-0.2.1 → auditable-0.4.0}/tests/test_mcp_integration.py +264 -264
  159. auditable-0.4.0/tests/test_methods_config.py +527 -0
  160. auditable-0.4.0/tests/test_methods_graph.py +289 -0
  161. auditable-0.4.0/tests/test_methods_pygod_step.py +49 -0
  162. auditable-0.4.0/tests/test_methods_registry.py +234 -0
  163. auditable-0.4.0/tests/test_methods_run_tabular.py +285 -0
  164. auditable-0.4.0/tests/test_methods_slice.py +127 -0
  165. auditable-0.4.0/tests/test_methods_spline_size.py +46 -0
  166. auditable-0.4.0/tests/test_methods_step_rules.py +327 -0
  167. auditable-0.4.0/tests/test_parity_consumption.py +66 -0
  168. {auditable-0.2.1 → auditable-0.4.0}/tests/test_public_api.py +143 -99
  169. {auditable-0.2.1 → auditable-0.4.0}/tests/test_session_graph.py +157 -157
  170. {auditable-0.2.1 → auditable-0.4.0}/tests/test_session_graph_projection.py +141 -141
  171. auditable-0.4.0/tests/test_version.py +11 -0
  172. auditable-0.2.1/PKG-INFO +0 -392
  173. auditable-0.2.1/README.md +0 -345
  174. {auditable-0.2.1 → auditable-0.4.0}/.gitignore +0 -0
  175. {auditable-0.2.1 → auditable-0.4.0}/LICENSE +0 -0
  176. {auditable-0.2.1 → auditable-0.4.0}/examples/example_audit_report.py +0 -0
  177. {auditable-0.2.1 → auditable-0.4.0}/examples/example_end_to_end.py +0 -0
  178. {auditable-0.2.1 → auditable-0.4.0}/examples/example_langgraph_live_replay.py +0 -0
  179. {auditable-0.2.1 → auditable-0.4.0}/examples/example_langgraph_llm_agent.py +0 -0
  180. {auditable-0.2.1 → auditable-0.4.0}/examples/example_live_monitor.py +0 -0
  181. {auditable-0.2.1 → auditable-0.4.0}/examples/example_live_replay.py +0 -0
  182. {auditable-0.2.1 → auditable-0.4.0}/examples/example_pre_lint_plan.py +0 -0
  183. {auditable-0.2.1 → auditable-0.4.0}/examples/example_standalone_report.py +0 -0
  184. {auditable-0.2.1 → auditable-0.4.0}/examples/example_touch_capture.py +0 -0
  185. {auditable-0.2.1 → auditable-0.4.0}/src/auditable/_report_figures.py +0 -0
  186. {auditable-0.2.1 → auditable-0.4.0}/src/auditable/compound.py +0 -0
  187. {auditable-0.2.1 → auditable-0.4.0}/src/auditable/graph/adapters/declared_plan.py +0 -0
  188. {auditable-0.2.1 → auditable-0.4.0}/src/auditable/graph/pre.py +0 -0
  189. {auditable-0.2.1 → auditable-0.4.0}/src/auditable/graph/touch.py +0 -0
  190. {auditable-0.2.1 → auditable-0.4.0}/src/auditable/integrations/langgraph.py +0 -0
  191. {auditable-0.2.1 → auditable-0.4.0}/src/auditable/model/__init__.py +0 -0
  192. {auditable-0.2.1 → auditable-0.4.0}/src/auditable/report.py +0 -0
  193. {auditable-0.2.1 → auditable-0.4.0}/tests/test_audit_report.py +0 -0
  194. {auditable-0.2.1 → auditable-0.4.0}/tests/test_auditors.py +0 -0
  195. {auditable-0.2.1 → auditable-0.4.0}/tests/test_basic.py +0 -0
  196. {auditable-0.2.1 → auditable-0.4.0}/tests/test_compound.py +0 -0
  197. {auditable-0.2.1 → auditable-0.4.0}/tests/test_gate.py +0 -0
  198. {auditable-0.2.1 → auditable-0.4.0}/tests/test_graph.py +0 -0
  199. {auditable-0.2.1 → auditable-0.4.0}/tests/test_graph_execution_reach.py +0 -0
  200. {auditable-0.2.1 → auditable-0.4.0}/tests/test_langgraph_integration.py +0 -0
  201. {auditable-0.2.1 → auditable-0.4.0}/tests/test_langgraph_replay_bridge.py +0 -0
  202. {auditable-0.2.1 → auditable-0.4.0}/tests/test_pre.py +0 -0
  203. {auditable-0.2.1 → auditable-0.4.0}/tests/test_pre_coverage.py +0 -0
  204. {auditable-0.2.1 → auditable-0.4.0}/tests/test_replay.py +0 -0
  205. {auditable-0.2.1 → auditable-0.4.0}/tests/test_report.py +0 -0
  206. {auditable-0.2.1 → auditable-0.4.0}/tests/test_sink.py +0 -0
  207. {auditable-0.2.1 → auditable-0.4.0}/tests/test_touch.py +0 -0
  208. {auditable-0.2.1 → auditable-0.4.0}/tests/test_touch_replay_bridge.py +0 -0
@@ -0,0 +1,42 @@
1
+ # Changelog
2
+
3
+ ## 0.4.0 (2026-11-14)
4
+
5
+ 0.4.0 adds leaderboards: `auditable.bench` reruns the tables of the CatchBench board on the same populations and protocols, and places any new method on them.
6
+
7
+ ### Added
8
+
9
+ - `auditable.bench`: `leaderboard(task_id, methods=None, extra=())` scores methods on a task and returns a ranked `Board` (`print`, `to_markdown()`, `to_dict()`, `compare_golden()`, `rank_of()`); `extra=[MyMethod]` adds a method that is not in the registry. A method whose extra is missing is listed as skipped with the extra named, and a method that raises is listed as an error while the rest of the board is still scored. Oracle rows, which read the labels, are shown as references and never ranked; control rows are ranked and marked.
10
+ - Six tasks, each reproducing a table of the CatchBench 0.1.3 board row by row: `detect/swegym` (376 runs) and `detect/tau` (660 runs), run-level failure detection by ROC-AUC; `live/swegym` and `live/tau`, early warning on 25, 50, 75 and 100 percent prefixes; `localize/whoandwhen` (126 failed runs), decisive-step localization by top-1, top-3 and MRR, including 31 LLM-judge rows replayed from CatchBench's committed prediction caches without an API call; and `pre/over_privilege` (1,187 configurations), configuration audit by F1, precision and recall, overall and per source. The populations are read through agentpile at the dataset revisions CatchBench pins, with the selection rules of GRADE commit 3839a57; the protocols are ported from CatchBench's adapters.
11
+ - `run.lr_spline_flat`, the flexible size control: a cubic spline of the four GRADE `flat` counts, then the board's logistic regression, ported from the `size (spline)` arm of CatchBench's size audits. It joins the `detect` and `live` boards as a control. On `detect/swegym` it ranks second, above both GRADE dependency regressions; on `detect/tau` it ranks below `run.lr_flatdep`. The library now holds 44 methods.
12
+ - Golden files with the board values of every row and the SHA-256 of the board they come from; the spline control's value comes from CatchBench's committed size audits instead, with a 0.001 tolerance. `python -m auditable.bench <task> --golden` compares a run with them and fails on a mismatch, on an error row, on a golden row left unchecked (skipped, missing a metric, absent from the task's default rows, or in a golden file that is missing), and on a run that compared no golden value; `--allow-skipped` accepts rows skipped for a missing extra. Three rows differ on macOS arm64 by a known platform effect and are reported as platform values; `--exact` counts them as mismatches, and the weekly Linux run uses it.
13
+ - `python -m auditable.bench`, the command-line entry: lists the tasks, prints a board, or emits it as Markdown or JSON.
14
+ - The `bench` extra (`pyod`, `scikit-learn`, `numpy`, `networkx`, `agentpile[hf]`); the graph rows also need `graph-ad`.
15
+ - CI: a `bench-core` job runs the bench unit tests, which download nothing, a weekly `bench golden` workflow reruns every task on Linux against its golden file, and a weekly `bench platforms` workflow installs the package from a clean checkout on Linux, macOS, and Windows (Python 3.10 and 3.13) with the README's graph-ad steps and checks three tasks against their golden files.
16
+
17
+ ## 0.3.0 (2026-10-25)
18
+
19
+ 0.3.0 turns `auditable` into a library of auditing methods for AI agents, with one interface per family and the ability to read public agent traces through agentpile.
20
+
21
+ ### Added
22
+
23
+ - `auditable.methods`: 43 methods in four families, each with one interface. The `config` family (8 methods) audits an agent configuration before a run, `online` (5) raises causal alarms while a run streams, and `run` (16) and `step` (14) detect a failed run and localize its decisive step afterwards. A registry (`list_methods`, `get_method`) returns each method's `MethodCard`, which records its fit scope, fidelity to its source, required fields, extras and citations. The library holds baselines from the literature (PyOD and PyGOD detectors, GUARDIAN-Lite, G-Safeguard-Lite, the Who&When LLM-judge protocols, OWASP-style configuration rules) beside the methods introduced with GRADE and CatchBench; every method that reproduces a CatchBench 0.1.2 board row matches it sample by sample.
24
+ - `auditable.featurize`: trace normalization for step lists, run dicts, typed graphs, `SessionGraph` and `TraceView` inputs, and the features the methods compute (the GRADE structural layers, the Who&When step matrix, the step dependency graph, PyG encodings).
25
+ - `auditable.llm`: a `prompt -> reply` backend protocol, `FixedResponseBackend` for tests and replays, `OpenAICompatibleBackend` (standard library only), and a prediction cache.
26
+ - `auditable.from_agentpile`, `configs_from_agentpile`, `step_targets`, `run_targets` and `auditable.datasets.load_traces` read agentpile records (public datasets, trace files, captured runs) into the methods' inputs and labels. `convention="grade"` with `deps="grade"` reproduces the steps, dependencies and graphs of GRADE's converters (commit 3839a57) on seven corpora; `convention="generic"` (the default) makes one step per message and one per tool call.
27
+ - Extras `tabular-ad`, `graph-ad` and `agentpile`; the core install still has no third-party dependency.
28
+ - `docs/methods-contract.md`, the contract a method follows and the behaviours a pinned consumer such as CatchBench relies on.
29
+
30
+ ### Behaviour of the Conversion
31
+
32
+ - In `convention="generic"`, content given as a list of parts (dicts with a string `type`, possibly mixed with bare strings) keeps its text and writes every other part (an image, a document) as `[<type>]`, dropping that part's other fields; `meta["report"]` counts those parts and says how each step of the record maps into the trace.
33
+ - `convention="grade"` raises `ValueError` for a run from a dataset revision other than the one its projection was checked on.
34
+ - A run with subagent trajectories is refused by default; the error names the two ways to read it, `from_agentpile(run.subagents, ...)` and `subagents="skip"`.
35
+
36
+ ### Deprecated
37
+
38
+ - `ActionGate`, `ReferenceLedger` and `DataAuditor` emit a `DeprecationWarning` when constructed and will be removed in a later release.
39
+
40
+ ## Earlier Releases
41
+
42
+ Releases up to 0.2.1 are listed on [PyPI](https://pypi.org/project/auditable/#history) and as [GitHub tags](https://github.com/yzhao062/auditable/tags).
@@ -0,0 +1,452 @@
1
+ Metadata-Version: 2.5
2
+ Name: auditable
3
+ Version: 0.4.0
4
+ Summary: A library of auditing methods for AI agents: configuration audits, run-level failure detection, step-level fault localization, and online monitors, behind one API per family.
5
+ Project-URL: Homepage, https://github.com/yzhao062/auditable
6
+ Project-URL: Repository, https://github.com/yzhao062/auditable
7
+ Project-URL: Issues, https://github.com/yzhao062/auditable/issues
8
+ Project-URL: Documentation, https://auditable-ai.readthedocs.io/
9
+ Author-email: Yue Zhao <yzhao010@usc.edu>
10
+ License-Expression: Apache-2.0
11
+ License-File: LICENSE
12
+ Keywords: agent,agent-reliability,ai-agents,ai-safety,anomaly-detection,audit,auditing,benchmark,failure-attribution,failure-detection,fault-localization,graph-anomaly-detection,langgraph,llm,mcp,trustworthy-ai
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: Apache Software License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
23
+ Classifier: Topic :: Software Development :: Quality Assurance
24
+ Requires-Python: >=3.9
25
+ Provides-Extra: agentpile
26
+ Requires-Dist: agentpile[hf]<0.4,>=0.3; extra == 'agentpile'
27
+ Requires-Dist: numpy>=1.21; extra == 'agentpile'
28
+ Provides-Extra: anomaly
29
+ Requires-Dist: pyod>=1.1.0; extra == 'anomaly'
30
+ Provides-Extra: bench
31
+ Requires-Dist: agentpile[hf]<0.4,>=0.3; extra == 'bench'
32
+ Requires-Dist: networkx>=3.0; extra == 'bench'
33
+ Requires-Dist: numpy>=1.21; extra == 'bench'
34
+ Requires-Dist: pyod>=1.1.0; extra == 'bench'
35
+ Requires-Dist: scikit-learn>=1.0; extra == 'bench'
36
+ Provides-Extra: dev
37
+ Requires-Dist: build>=1.0; extra == 'dev'
38
+ Requires-Dist: pyod>=1.1.0; extra == 'dev'
39
+ Requires-Dist: pytest>=7.0; extra == 'dev'
40
+ Requires-Dist: twine>=5.0; extra == 'dev'
41
+ Provides-Extra: graph
42
+ Requires-Dist: networkx>=3.0; extra == 'graph'
43
+ Provides-Extra: graph-ad
44
+ Requires-Dist: networkx>=3.0; extra == 'graph-ad'
45
+ Requires-Dist: numpy>=1.21; extra == 'graph-ad'
46
+ Requires-Dist: pygod>=1.1.0; extra == 'graph-ad'
47
+ Requires-Dist: pyod>=1.1.0; extra == 'graph-ad'
48
+ Requires-Dist: scikit-learn>=1.0; extra == 'graph-ad'
49
+ Requires-Dist: torch-geometric>=2.3; extra == 'graph-ad'
50
+ Requires-Dist: torch>=2.0; extra == 'graph-ad'
51
+ Provides-Extra: langchain
52
+ Requires-Dist: langchain-core<2,>=0.3.0; extra == 'langchain'
53
+ Provides-Extra: langgraph
54
+ Requires-Dist: langgraph>=1.0; (python_version >= '3.10') and extra == 'langgraph'
55
+ Requires-Dist: networkx>=3.0; extra == 'langgraph'
56
+ Provides-Extra: llm
57
+ Requires-Dist: openai>=1.0; extra == 'llm'
58
+ Provides-Extra: mcp
59
+ Requires-Dist: mcp<2,>=1.28.1; (python_version >= '3.10') and extra == 'mcp'
60
+ Provides-Extra: report
61
+ Requires-Dist: fpdf2>=2.7; extra == 'report'
62
+ Requires-Dist: matplotlib>=3.5; extra == 'report'
63
+ Requires-Dist: networkx>=3.0; extra == 'report'
64
+ Provides-Extra: tabular-ad
65
+ Requires-Dist: networkx>=3.0; extra == 'tabular-ad'
66
+ Requires-Dist: numpy>=1.21; extra == 'tabular-ad'
67
+ Requires-Dist: pyod>=1.1.0; extra == 'tabular-ad'
68
+ Requires-Dist: scikit-learn>=1.0; extra == 'tabular-ad'
69
+ Description-Content-Type: text/markdown
70
+
71
+ <div align="center">
72
+
73
+ <!-- logo-family -->
74
+ <picture>
75
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/yzhao062/auditable/main/docs/assets/brand/logo-dark.svg">
76
+ <img src="https://raw.githubusercontent.com/yzhao062/auditable/main/docs/assets/brand/logo.svg" alt="auditable" width="350">
77
+ </picture>
78
+
79
+
80
+ # auditable
81
+
82
+ **A library of auditing methods for AI agents, with a leaderboard for each task: configuration audits before a run, online monitors while it runs, and failure detection and fault localization after it.**
83
+
84
+ [![PyPI](https://img.shields.io/pypi/v/auditable.svg)](https://pypi.org/project/auditable/)
85
+ [![Python](https://img.shields.io/pypi/pyversions/auditable.svg)](https://pypi.org/project/auditable/)
86
+ [![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](https://github.com/yzhao062/auditable/blob/main/LICENSE)
87
+ [![CI](https://github.com/yzhao062/auditable/actions/workflows/ci.yml/badge.svg)](https://github.com/yzhao062/auditable/actions/workflows/ci.yml)
88
+ [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/yzhao062/auditable/blob/main/examples/leaderboard.ipynb)
89
+
90
+ [Leaderboard](#leaderboard) · [Put Your Method on the Board](#put-your-method-on-the-board) · [Tasks](#tasks) · [Quick Start](#quick-start) · [Real Agent Traces](#real-agent-traces) · [Methods](#methods) · [Install](#install) · [Adding a Method](#adding-a-method) · [Citation](#citation)
91
+
92
+ </div>
93
+
94
+ `auditable` is a library of auditing methods for AI agents with a leaderboard for each task: one command reruns the [CatchBench](https://github.com/yzhao062/catchbench) board on your machine, and a method you write gets a rank on the same population and protocol.
95
+
96
+ ## Leaderboard
97
+
98
+ Did the run fail? The board below ranks 17 methods by ROC-AUC on 376 SWE-Gym OpenHands runs, 188 of which failed. One command reruns it on the population and protocol of the CatchBench board:
99
+
100
+ ```bash
101
+ pip install "auditable[bench]"
102
+ python -m auditable.bench detect/swegym
103
+ ```
104
+
105
+ | Rank | Method | Algorithm | ROC-AUC |
106
+ |---|---|---|---|
107
+ | 1 | `run.pygod_gaan` | GAAN adversarial detector (PyGOD) | 0.850 |
108
+ | 2 | `run.lr_spline_flat` | Logistic regression on a cubic spline of run size, a control | 0.832 |
109
+ | 3 | `run.gsafeguard_lite` | G-Safeguard-Lite (supervised graph classifier) | 0.828 |
110
+ | 4 | `run.lr_full` | Logistic regression on all raw layers (GRADE full) | 0.819 |
111
+ | 5 | `run.lr_flatdep` | Logistic regression on size and dependency (GRADE flatdep) | 0.804 |
112
+ | 6 | `run.guardian_lite` | GUARDIAN-Lite (attribute-reconstruction autoencoder) | 0.767 |
113
+ | 7 | `run.pyod_ecod` | ECOD on run size | 0.765 |
114
+ | 8 | `run.pygod_conad` | CONAD contrastive graph autoencoder (PyGOD) | 0.750 |
115
+ | 9 | `run.lr_flat` | Logistic regression on run size (GRADE flat), the size baseline | 0.663 |
116
+ | 10 | `run.pyod_copod` | COPOD on run size | 0.625 |
117
+ | 11 | `run.pygod_anomalydae` | AnomalyDAE dual autoencoder (PyGOD) | 0.592 |
118
+ | 12 | `run.pyod_lof` | Local outlier factor on run size | 0.584 |
119
+ | 13 | `run.pyod_iforest` | Isolation Forest on run size | 0.571 |
120
+ | 14 | `run.pygod_dominant` | DOMINANT graph autoencoder (PyGOD) | 0.547 |
121
+ | 15 | `run.random` | Random run scores, a control | 0.483 |
122
+ | 16 | `run.pyod_knn` | KNN distance on run size | 0.446 |
123
+ | 17 | `run.pyod_hbos` | HBOS on run size | 0.319 |
124
+
125
+ PyGOD's GAAN, a literature baseline, leads this board at 0.850. Second, at 0.832, is the flexible size control `run.lr_spline_flat`: a cubic spline of the same four run-size counts the linear size baseline `run.lr_flat` reads (0.663). Run size alone, fitted without a linear restriction, ranks above G-Safeguard-Lite and above both GRADE dependency regressions (0.819 and 0.804), so this board does not show that dependency structure predicts failure beyond size, the reading GRADE retired on 2026-09-08. Read a new row against both size controls; on tau-bench the order changes, and `run.lr_flatdep` (0.665) ranks above the spline control (0.631).
126
+
127
+ The 16 CatchBench rows equal the CatchBench board, which was computed on Linux; the spline control is not a board row, and its reference value is the `size (spline)` arm of CatchBench's committed size audit. On macOS, `run.pyod_lof` prints 0.582 instead of 0.584, and `--golden` reports that as a platform value and not as a mismatch. The first run downloads the data through agentpile; later runs load SWE-Gym from the cache in about 3 seconds, and the full board takes about 70 seconds on a laptop CPU, of which PyGOD GAAN is the slowest row at about 37 seconds. The graph rows need `auditable[graph-ad]` and the PyG sampling backend ([Install](#install)); without them those rows are listed as skipped with the extra named, and the rest of the board is still scored.
128
+
129
+ ```bash
130
+ python -m auditable.bench # list the tasks
131
+ python -m auditable.bench detect/swegym --methods run.lr_flat run.pyod_ecod # score chosen rows only
132
+ python -m auditable.bench detect/swegym --golden # exit 1 if a row differs from the CatchBench board
133
+ python -m auditable.bench detect/swegym --markdown # a Markdown table; --json prints JSON
134
+ ```
135
+
136
+ ## Put Your Method on the Board
137
+
138
+ A method is a class with a `MethodCard`. Pass it as `extra=` and `leaderboard` scores it beside the default rows, under the protocol of its kind: a supervised detector gets the mean ROC-AUC over five seeds of stratified 5-fold cross-validation with a fresh estimator per fold, and an unsupervised one gets a single `fit_score` over the whole population. The snippet below adds a random forest on run size, a deliberately plain method:
139
+
140
+ ```python
141
+ from sklearn.ensemble import RandomForestClassifier
142
+
143
+ from auditable.base import BaseRunDetector, MethodCard
144
+ from auditable.bench import leaderboard
145
+ from auditable.featurize import grade_layer_matrix
146
+
147
+
148
+ class ForestOnSize(BaseRunDetector):
149
+ card = MethodCard(method_id="run.forest_flat", name="Random forest on run size", family="run",
150
+ fit_scope="inductive", supervised=True, output_kind="score", fidelity="original")
151
+
152
+ def fit(self, runs, y=None):
153
+ self.model_ = RandomForestClassifier(200, random_state=0)
154
+ self.model_.fit(grade_layer_matrix(list(runs), "flat"), y)
155
+ self.decision_scores_ = self.decision_function(runs)
156
+ return self
157
+
158
+ def decision_function(self, runs):
159
+ return self.model_.predict_proba(grade_layer_matrix(list(runs), "flat"))[:, 1]
160
+
161
+
162
+ board = leaderboard("detect/swegym", extra=[ForestOnSize])
163
+ print(board.rank_of("run.forest_flat"), board["run.forest_flat"].scores)
164
+ ```
165
+
166
+ The last line prints the rank of the new row among all rows and its scores. `print(board)` shows the full table, with the CatchBench value in brackets beside each row that has one, and `board.to_markdown()`, `board.to_dict()` and `board.compare_golden()` export it. `methods=[...]` scores only the rows you name, which is quick while you iterate (`leaderboard("detect/swegym", methods=["run.lr_flat", ForestOnSize])`), and `Entry(method, kwargs={...}, label="...", key="...")` adds a row for a variant with constructor arguments (give each variant its own key, because a row that keeps the method id is compared with that method's CatchBench value). A method that raises is listed as an error and the rest of the board is still scored. The [method contract](https://github.com/yzhao062/auditable/blob/main/docs/methods-contract.md) lists the conventions for the four families.
167
+
168
+ ## Tasks
169
+
170
+ Six tasks ship with 0.4.0. Each fixes a population and a protocol, so every method on a board is scored on the same data in the same way. `detect` scores the run detectors, `localize` the step locators, `live` the online monitors (run detectors also take part, scored on prefixes of the run), and `pre` the configuration auditors.
171
+
172
+ | Task | Question | Population | Metric |
173
+ |---|---|---|---|
174
+ | `detect/swegym` | Did the run fail? | SWE-Gym OpenHands trajectories, one scaffold: 376 runs, 188 failed | ROC-AUC |
175
+ | `detect/tau` | Did the run fail? | tau-bench: 660 runs, 363 failed | ROC-AUC |
176
+ | `localize/whoandwhen` | Which step was the decisive mistake? | Who&When: 126 failed multi-agent runs with a human mistake step | top-1, top-3, MRR (mean reciprocal rank) |
177
+ | `live/swegym` | Can the failure be caught early? | SWE-Gym runs, read as prefixes of 25, 50, 75 and 100 percent of their steps | mean prefix ROC-AUC |
178
+ | `live/tau` | Can the failure be caught early? | tau-bench runs, read as prefixes of 25, 50, 75 and 100 percent of their steps | mean prefix ROC-AUC |
179
+ | `pre/over_privilege` | Is the agent configuration over-privileged before it runs? | 1,187 configurations from six sources | F1, precision, recall |
180
+
181
+ Every task id works with the commands above: `python -m auditable.bench <task>` reruns the board, and `leaderboard("<task>", extra=[...])` places a method on it. The LLM judges on `localize/whoandwhen` and `pre/over_privilege` are replayed from committed caches, so those boards need no API call.
182
+
183
+ The CatchBench board shows how the other tasks read. On Who&When localization, the best LLM judges (all-at-once) reach top-1 of about 0.42 to 0.45 against 0.119 for random, and the structural blast rule ties the position prior at 0.159 top-1. The tau-bench detection board is led by `run.lr_flatdep` and `run.lr_full`, tied at 0.665, against 0.619 for the size baseline. For over-privilege, the best rule scanner `config.owasp_asi_combined` reaches F1 0.654, flagging every configuration reaches 0.601, and an LLM judge reaches 0.695.
184
+
185
+ ## What Is in the Library
186
+
187
+ `auditable` collects methods that audit LLM agents at three points: an agent configuration before it runs (PRE), a run while it streams (LIVE), and a finished run (POST). Methods are grouped into four families, and each family has one interface, in the way PyOD gives outlier detectors one interface. The library holds baselines from the literature (PyOD and PyGOD detectors, GUARDIAN, G-Safeguard, the Who&When LLM-judge protocols, OWASP-style configuration rules) beside the methods introduced with GRADE and CatchBench.
188
+
189
+ [CatchBench](https://github.com/yzhao062/catchbench) is the paper and benchmark whose board the leaderboards reproduce: `auditable.bench` carries its populations and evaluation protocols, and every method here that appears on its board reproduces its row sample by sample ([Reproducing CatchBench](#reproducing-catchbench)). [agentpile](https://github.com/yzhao062/agentpile) is the converter that reads public agent-trace datasets into the one record the methods consume ([Real Agent Traces](#real-agent-traces)).
190
+
191
+ ## Quick Start
192
+
193
+ Install with `pip install "auditable[tabular-ad]"` (see [Install](#install)), then:
194
+
195
+ ```python
196
+ from sklearn.metrics import roc_auc_score
197
+
198
+ from auditable.datasets import make_traces
199
+ from auditable.methods import get_method
200
+
201
+ # 100 toy agent runs; each failed run holds one stale read (a step relying on old state)
202
+ traces, y, mistakes = make_traces(n_runs=100, contamination=0.5, random_state=0)
203
+ train, test = slice(0, 50), slice(50, 100)
204
+
205
+ # Did the run fail? Fit on labelled runs, then score new ones, as with a PyOD detector.
206
+ detector = get_method("run.lr_flatdep")()
207
+ detector.fit(traces[train], y[train])
208
+ scores = detector.decision_function(traces[test]) # one score per run, higher = more likely failed
209
+ print(f"run-level ROC-AUC: {roc_auc_score(y[test], scores):.3f}")
210
+
211
+ # Which step was the mistake? Rank the steps of each failed run, most suspected first.
212
+ locator = get_method("step.dep_anomaly")()
213
+ failed = [i for i in range(100) if y[i] == 1]
214
+ hits = sum(locator.rank(traces[i])[0] == mistakes[i] for i in failed)
215
+ print(f"mistake ranked first in {hits} of {len(failed)} failed runs")
216
+
217
+ # Can it be caught live? One causal alarm per arriving step (step t sees steps 0..t only).
218
+ monitor = get_method("online.span_z")()
219
+ alarms = monitor.step_alarm_scores(traces[failed[0]])
220
+ print(f"first alarm peak at step {alarms.argmax()}, mistake at step {mistakes[failed[0]]}")
221
+ ```
222
+
223
+ Every method is created from its ID with `get_method(id)()`, and the methods of one family share one interface, so trying another detector or locator changes one string. [`examples/example_methods.py`](https://github.com/yzhao062/auditable/blob/main/examples/example_methods.py) adds a configuration audit before the run.
224
+
225
+ ## Real Agent Traces
226
+
227
+ [agentpile](https://github.com/yzhao062/agentpile) loads public agent-trace datasets (Who&When, TRAIL, MAST, tau-bench, SWE-Gym and others), trace files (OpenAI-style messages, OTLP JSON, Claude Code sessions, MCP tool lists) and captured runs into one record. `from_agentpile` turns those records into the traces every method reads, and `configs_from_agentpile` turns them into the configurations the config methods read. `pip install "auditable[agentpile]"` installs the conversion; the example below also runs tabular methods, so it needs `pip install "auditable[agentpile,tabular-ad]"`.
228
+
229
+ ```python
230
+ import agentpile as ap
231
+ import auditable as au
232
+ from auditable.datasets import load_traces
233
+ from auditable.methods import get_method
234
+
235
+ # Any run: name the dependency rules, since agentpile infers none when it loads.
236
+ runs = list(ap.load("tau_bench", limit=100))
237
+ traces = au.from_agentpile(runs, deps=["tool_call_id_v1", "value_flow_v1"])
238
+ scores = get_method("run.pyod_ecod")().fit_score(traces) # one score per run
239
+ y = au.run_targets(traces) # 1 failed, 0 succeeded, -1 unknown
240
+
241
+ # The same shapes as make_traces, on a public dataset; mistakes holds the labeled step (-1 if none).
242
+ traces, y, mistakes = load_traces("who_and_when", deps="full_context", subset="Algorithm-Generated")
243
+ labeled = [t for t, m in zip(traces, mistakes) if m >= 0]
244
+ print(get_method("step.dep_anomaly")().rank(labeled[0])[:3])
245
+
246
+ # Before a run: an MCP tools/list result plus the task the agent will get.
247
+ configs = au.configs_from_agentpile(next(ap.load("tools.json")), task="Summarize the open issues.")
248
+ print(get_method("config.owasp_asi_combined")().audit(configs[0]))
249
+ ```
250
+
251
+ `deps` has no default. It is a list of agentpile rules, `"recorded"` for the dependencies the record already holds, or `"full_context"` for every earlier step, and each trace's `meta` keeps the rules that ran. The default `convention="generic"` makes one step per message and one per tool call with its result. `convention="grade"` with `deps="grade"` instead reproduces the steps, dependencies and graphs of the GRADE converters that CatchBench and the GRADE paper used, for the seven corpora below. [`benchmarks/agentpile_crosscheck`](https://github.com/yzhao062/auditable/tree/main/benchmarks/agentpile_crosscheck) compares that output with GRADE's run by run.
252
+
253
+ | Corpus | `load_traces` arguments | Runs in GRADE's output | Result |
254
+ |---|---|---|---|
255
+ | Who&When | `"who_and_when", convention="grade", deps="grade"` | 184 | equal; the 126 localization labels match |
256
+ | tau-bench | `"tau_bench", convention="grade", deps="grade", model=[...]` | 660 | equal |
257
+ | tau2-bench | `"tau2_bench", convention="grade", deps="grade", model=...` | 278 | equal to the frozen rule `tau2-frozen-v1`, since GRADE's own tau2 output changes with the hash seed |
258
+ | SWE-Gym | `"swe_gym_openhands", convention="grade", deps="grade"` | 376 | equal |
259
+ | AgentRewardBench | `"agent_reward_bench", convention="grade", deps="grade"` | 600 | equal on 3 runs; on the other 597, GRADE adds two steps for the page after the last action, which agentpile does not create, and the steps before them are equal |
260
+ | SWE-rebench OpenHands | `"swe_rebench_openhands", convention="grade", deps="grade"` | 600 | equal |
261
+ | SWE-agent | `"swe_agent_nebius", convention="grade", deps="grade"` | 600 | equal |
262
+
263
+ These results hold on the dataset revisions agentpile 0.3.0 loads by default. On any other revision, `convention="grade"` raises an error rather than return steps nobody has compared with GRADE's, and `convention="generic"` reads the run without that guarantee.
264
+
265
+ In the generic convention, a `user` step is a step the record attributes to the user role, which is not always text a person typed (a session's compact summary or a task notification has that role too). Content given as a list of parts (dicts with a string `type`, possibly mixed with bare strings) keeps its text; an image, a document or any other non-text part becomes a placeholder such as `[image]` and its other fields are dropped, while any other list stays JSON. `meta["report"]` counts the replaced parts and accounts for every step of the record.
266
+
267
+ A run with subagent trajectories (a Claude Code session with subagents, an Agents SDK handoff) raises an error, since this version does not flatten them into one trace. `from_agentpile(run.subagents, ...)` reads each subagent as a trace of its own, and `subagents="skip"` reads the top-level trajectory only; on the Claude Code sessions with subagents we measured, skipping left out about half of the steps.
268
+
269
+ ## Methods
270
+
271
+ Configuration rules and the step-by-step and binary-search LLM judges run on the core install, graph detectors need the `graph-ad` extra, and every other method needs `tabular-ad`. The tables are generated from the registry: `list_methods(family=...)` returns each method's card, and `method_table(detailed=True)` prints the fit scope, fidelity to the source, extras, and the CatchBench row each method reproduces. The [method contract](https://github.com/yzhao062/auditable/blob/main/docs/methods-contract.md) defines the four interfaces on one page.
272
+
273
+ ### Config: Is the Agent Over-Privileged Before It Runs?
274
+
275
+ `auditor.audit(config)` reads an `AgentConfig` (the declared capabilities and the task text) and returns a finding: `flag` (True, False, or None to abstain), the `excess` capabilities, and the `rules` that fired.
276
+
277
+ <!-- methods:config:start -->
278
+ | Type | Method | Algorithm | Reference |
279
+ |---|---|---|---|
280
+ | Rule | `config.flag_risky_perms` | Flag risky permission levels | [CatchBench (2026)](https://arxiv.org/abs/2608.22808) |
281
+ | Rule | `config.owasp_asi_combined` | Combined OWASP and CWE scanner | [CatchBench (2026)](https://arxiv.org/abs/2608.22808) |
282
+ | Rule | `config.owasp_excess_functionality` | Excess functionality | [CatchBench (2026)](https://arxiv.org/abs/2608.22808) |
283
+ | Rule | `config.owasp_excess_permissions` | Excess permission level | [CatchBench (2026)](https://arxiv.org/abs/2608.22808) |
284
+ | Rule | `config.owasp_privilege_escalation` | Unrequested privilege management | [CatchBench (2026)](https://arxiv.org/abs/2608.22808) |
285
+ | Rule | `config.sensitive_access` | Unrequested sensitive access | [CatchBench (2026)](https://arxiv.org/abs/2608.22808) |
286
+ | Rule | `config.unrequested_high_impact` | Unrequested high-impact action | [CatchBench (2026)](https://arxiv.org/abs/2608.22808) |
287
+ | LLM judge | `config.llm_needed` | LLM judge, needed capabilities | [CatchBench (2026)](https://arxiv.org/abs/2608.22808) |
288
+ <!-- methods:config:end -->
289
+
290
+ ### Online: Can the Failure Be Caught While the Run Streams?
291
+
292
+ `monitor.step_alarm_scores(trace)` returns one alarm per step, and the alarm at step t reads steps 0 to t only.
293
+
294
+ <!-- methods:online:start -->
295
+ | Type | Method | Algorithm | Reference |
296
+ |---|---|---|---|
297
+ | Rule | `online.dep_count_z` | Causal z-score of dependency count | [CatchBench (2026)](https://arxiv.org/abs/2608.22808) |
298
+ | Rule | `online.dep_span` | Mean dependency span (running) | [CatchBench (2026)](https://arxiv.org/abs/2608.22808) |
299
+ | Rule | `online.raw_span` | Raw dependency span | [CatchBench (2026)](https://arxiv.org/abs/2608.22808) |
300
+ | Rule | `online.span_z` | Causal z-score of dependency span | [CatchBench (2026)](https://arxiv.org/abs/2608.22808) |
301
+ | Baseline | `online.random` | Random alarm scores | [CatchBench (2026)](https://arxiv.org/abs/2608.22808) |
302
+ <!-- methods:online:end -->
303
+
304
+ ### Run: Did the Run Fail?
305
+
306
+ `detector.fit(traces, y)`, then `detector.decision_function(traces)`: one score per run, higher meaning more likely failed. Unsupervised detectors ignore `y`. Transductive detectors (the PyGOD detectors and GUARDIAN-Lite) fit and score one batch together with `detector.fit_score(traces)`.
307
+
308
+ <!-- methods:run:start -->
309
+ | Type | Method | Algorithm | Reference |
310
+ |---|---|---|---|
311
+ | Supervised | `run.lr_flat` | Logistic regression on run size (GRADE flat) | [GRADE (Zhao, 2026)](https://arxiv.org/abs/2606.22741) |
312
+ | Supervised | `run.lr_flatdep` | Logistic regression on size and dependency (GRADE flatdep) | [GRADE (Zhao, 2026)](https://arxiv.org/abs/2606.22741) |
313
+ | Supervised | `run.lr_full` | Logistic regression on all raw layers (GRADE full) | [GRADE (Zhao, 2026)](https://arxiv.org/abs/2606.22741) |
314
+ | Supervised | `run.lr_spline_flat` | Logistic regression on a cubic spline of run size (flexible size control) | [CatchBench (2026)](https://arxiv.org/abs/2608.22808) |
315
+ | PyOD detector | `run.pyod_copod` | COPOD on run size | [COPOD (Li et al., 2020)](https://arxiv.org/abs/2009.09463) |
316
+ | PyOD detector | `run.pyod_ecod` | ECOD on run size | [ECOD (Li et al., 2022)](https://arxiv.org/abs/2201.00382) |
317
+ | PyOD detector | `run.pyod_hbos` | HBOS on run size | HBOS (Goldstein and Dengel, 2012) |
318
+ | PyOD detector | `run.pyod_iforest` | Isolation Forest on run size | [Isolation Forest (Liu et al., 2008)](https://doi.org/10.1109/ICDM.2008.17) |
319
+ | PyOD detector | `run.pyod_knn` | KNN distance on run size | KNN outlier score (Ramaswamy et al., 2000) |
320
+ | PyOD detector | `run.pyod_lof` | Local outlier factor on run size | [LOF (Breunig et al., 2000)](https://doi.org/10.1145/335191.335388) |
321
+ | Graph detector | `run.gsafeguard_lite` | G-Safeguard-Lite (supervised graph classifier) | [G-Safeguard (Wang et al., 2025)](https://arxiv.org/abs/2502.11127) |
322
+ | Graph detector | `run.guardian_lite` | GUARDIAN-Lite (attribute-reconstruction autoencoder) | [GUARDIAN (Zhou et al., 2025)](https://arxiv.org/abs/2505.19234) |
323
+ | Graph detector | `run.pygod_anomalydae` | AnomalyDAE dual autoencoder (PyGOD) | AnomalyDAE (Fan et al., 2020) |
324
+ | Graph detector | `run.pygod_conad` | CONAD contrastive graph autoencoder (PyGOD) | CONAD (Xu et al., 2022) |
325
+ | Graph detector | `run.pygod_dominant` | DOMINANT graph autoencoder (PyGOD) | [DOMINANT (Ding et al., 2019)](https://doi.org/10.1137/1.9781611975673.67) |
326
+ | Graph detector | `run.pygod_gaan` | GAAN adversarial detector (PyGOD) | GAAN (Chen et al., 2020) |
327
+ | Baseline | `run.random` | Random run scores | [CatchBench (2026)](https://arxiv.org/abs/2608.22808) |
328
+ <!-- methods:run:end -->
329
+
330
+ ### Step: Which Step Was the Decisive Mistake?
331
+
332
+ `locator.rank(trace)` orders the steps of a failed run, most suspected first, and `locator.step_scores(trace)` gives their scores. Supervised locators are first fitted with `fit(traces, y)`, where `y` holds the `idx` of each run's mistake. The step-by-step and binary-search LLM judges name one step with `predict_step(trace)`.
333
+
334
+ <!-- methods:step:start -->
335
+ | Type | Method | Algorithm | Reference |
336
+ |---|---|---|---|
337
+ | Rule | `step.blast` | Blast radius (downstream reach) | [CatchBench (2026)](https://arxiv.org/abs/2608.22808) |
338
+ | Rule | `step.degree` | Dependency degree | [CatchBench (2026)](https://arxiv.org/abs/2608.22808) |
339
+ | Rule | `step.dep_anomaly` | Dependency anomaly (within-run z-score) | [CatchBench (2026)](https://arxiv.org/abs/2608.22808) |
340
+ | Rule | `step.has_dep` | Has a dependency | [CatchBench (2026)](https://arxiv.org/abs/2608.22808) |
341
+ | Rule | `step.max_span` | Maximum dependency span | [CatchBench (2026)](https://arxiv.org/abs/2608.22808) |
342
+ | Rule | `step.position_early_rule` | Early-position prior | [CatchBench (2026)](https://arxiv.org/abs/2608.22808) |
343
+ | Supervised | `step.exec_rank` | Execution-feature step ranker | [GRADE (Zhao, 2026)](https://arxiv.org/abs/2606.22741) |
344
+ | Supervised | `step.position_supervised` | Early-fault position prior | [GRADE (Zhao, 2026)](https://arxiv.org/abs/2606.22741) |
345
+ | Graph detector | `step.pygod_dominant_sg4` | DOMINANT on the step dependency graph | [DOMINANT (Ding et al., 2019)](https://doi.org/10.1137/1.9781611975673.67) |
346
+ | Graph detector | `step.pygod_dominant_ww8` | DOMINANT on the Who&When step matrix | [DOMINANT (Ding et al., 2019)](https://doi.org/10.1137/1.9781611975673.67) |
347
+ | LLM judge | `step.llm_all_at_once` | LLM judge, all-at-once | [Who&When (Zhang et al., 2025)](https://arxiv.org/abs/2505.00212) |
348
+ | LLM judge | `step.llm_binary_search` | LLM judge, binary-search | [Who&When (Zhang et al., 2025)](https://arxiv.org/abs/2505.00212) |
349
+ | LLM judge | `step.llm_step_by_step` | LLM judge, step-by-step | [Who&When (Zhang et al., 2025)](https://arxiv.org/abs/2505.00212) |
350
+ | Baseline | `step.random` | Random step scores | [CatchBench (2026)](https://arxiv.org/abs/2608.22808) |
351
+ <!-- methods:step:end -->
352
+
353
+ ## Install
354
+
355
+ ```bash
356
+ pip install auditable # core: the registry and the configuration rules, no third-party dependencies
357
+ pip install "auditable[bench]" # the leaderboards: PyOD, scikit-learn, NumPy, NetworkX, and agentpile with its Hugging Face loaders
358
+ pip install "auditable[tabular-ad]" # run, step, and online methods on trace features (NumPy, scikit-learn, PyOD, NetworkX)
359
+ pip install "auditable[agentpile]" # read public datasets, trace files and captured runs through agentpile
360
+ ```
361
+
362
+ The examples, tests and benchmark harnesses are in the repository, not the wheel; to run them, install from a clone with `pip install -e ".[dev,tabular-ad,bench]"`.
363
+
364
+ The LLM judges take any `prompt -> reply` function as their backend; `OpenAICompatibleBackend` reaches an OpenAI-compatible endpoint with the standard library alone.
365
+
366
+ The graph methods (PyGOD detectors, GUARDIAN-Lite, G-Safeguard-Lite), and so the graph rows of the leaderboards, need torch, PyTorch Geometric, PyGOD, and a sampling backend built for the installed torch, which pip cannot resolve from the extra; they need Python 3.10 or newer, since torch 2.12.1 and `pyg-lib` publish no wheels for 3.9. Install torch and the matching `pyg-lib` wheel first (the same steps on Linux, macOS on Apple silicon, and Windows):
367
+
368
+ ```bash
369
+ pip install "torch==2.12.1" --index-url https://download.pytorch.org/whl/cpu
370
+ pip install pyg_lib -f https://data.pyg.org/whl/torch-2.12.1+cpu.html
371
+ pip install "auditable[graph-ad]"
372
+ python -c "import torch_geometric.typing as t; assert t.WITH_PYG_LIB"
373
+ ```
374
+
375
+ Where torch is already installed, as on Colab, keep it: skip the first line, and in the `pyg-lib` URL replace `2.12.1+cpu` with the installed version, which `python -c "import torch; print(torch.__version__)"` prints; the pinned CPU build would replace Colab's torch and break its preinstalled torchvision. PyG does not publish wheels for every patch release, so when pip finds no `pyg_lib` at that URL, use the `.0` release with the same CUDA tag: Colab's torch `2.14.1+cu130` takes `https://data.pyg.org/whl/torch-2.14.0+cu130.html`. The graph-ad install was checked for 0.4.0 on a Colab T4 runtime with its torch 2.14.1+cu130, and for 0.3.0 on Colab CPU and T4 runtimes with Colab's torch and on Linux aarch64 (DGX Spark) with the pinned one; a weekly workflow (`bench-platforms.yml`) repeats the pinned steps from a clean install on Linux, macOS, and Windows and checks three boards against their golden files. The PyGOD methods run on the CPU (`gpu=-1`) even when a GPU is present.
376
+
377
+ ## Traces and Inputs
378
+
379
+ Methods read traces through `auditable.featurize.normalize_trace`, which accepts a list of step dicts, a run dict (`{"steps": ..., "texts": ...}`), a typed NetworkX graph from `auditable.graph.build_graph`, a `SessionGraph`, or the `TraceView` that `from_agentpile` returns. Steps carry `idx`, `agent`, `kind` (`"decision"` or `"tool_call"`), and optionally `deps` and `text`; a `SessionGraph` also keeps parallel branches and merges of the execution order. A trace without dependency annotations can be read with `dependency="full_context"`, the assumption that each step relied on every earlier step. A method that needs a field the trace lacks (step text for an LLM judge, for example) raises an error naming it.
380
+
381
+ `auditable.featurize` exposes the features the methods compute, so they can be inspected or reused: the GRADE structural layers (`grade_layer`, `grade_layer_matrix`), the Who&When step matrix (`who_when_step_matrix`), the step dependency graph and its statistics (`step_graph`, `span_series`, `run_max_span`), and graph encodings for PyGOD (`to_pyg`).
382
+
383
+ ## Reproducing CatchBench
384
+
385
+ `python -m auditable.bench <task> --golden` reruns a CatchBench board with the methods of this library and exits 1 if a row differs from the board ([Leaderboard](#leaderboard)). The parity harness [`benchmarks/catchbench_parity`](https://github.com/yzhao062/auditable/tree/main/benchmarks/catchbench_parity) remains for development and is not part of the wheel. It compares every migrated method with its CatchBench or GRADE original sample by sample, at CatchBench 0.1.2 and GRADE commit `3839a57`, the last versions whose board computes those entries with its own code.
386
+
387
+ ```bash
388
+ export GRADE_DIR=/path/to/grade # checked out at 3839a57
389
+ export PYTHONHASHSEED=0
390
+ python benchmarks/catchbench_parity/run_parity.py checks # per-sample comparisons
391
+ python benchmarks/catchbench_parity/run_parity.py board --out board.txt # the full board through the library
392
+ python benchmarks/catchbench_parity/run_parity.py board --perturb --out perturbed.txt
393
+ python benchmarks/catchbench_parity/run_parity.py consumption board.txt perturbed.txt
394
+ ```
395
+
396
+ ## Adding a Method
397
+
398
+ Subclass the family base class in `auditable.base`, give it a `MethodCard`, and register it with `@register`, which makes it reachable by id (`get_method`, `--methods`) and adds it to the default rows of the detect and pre boards. To try a method first, pass it to `leaderboard(task, extra=[...])` without registering ([Put Your Method on the Board](#put-your-method-on-the-board)). The [method contract](https://github.com/yzhao062/auditable/blob/main/docs/methods-contract.md) lists the conventions: input forms, label alignment for supervised step methods, output order, optional dependencies, the tests a method needs, and how to add a parity check when the method reproduces a CatchBench row.
399
+
400
+ ## Records and Graph Analysis
401
+
402
+ The package also contains the modules it was first built around. `audit` and `replay` capture an agent decision with the dependency state it relied on and re-decide it against later state; `analyze_run` ranks the steps of a finished run by structural blast share; `analyze_plan` lints a declared plan; `LiveSession` scores a growing run; and `auditable.integrations` captures observed dependency edges from LangGraph, LangChain, MCP servers, or any loop through `TouchRecorder`. See [`examples/README.md`](https://github.com/yzhao062/auditable/blob/main/examples/README.md) and the [documentation](https://auditable-ai.readthedocs.io/).
403
+
404
+ Records are content-addressed and hash-chained, and that property is limited. A signature or digest authenticates only the entries that were submitted, a hash chain exposes a later edit only when its head is anchored with an independent party, and neither reveals an event that was never written. The chain therefore does not establish that a log is complete.
405
+
406
+ ### Legacy
407
+
408
+ `ActionGate`, `ReferenceLedger` (the in-process reference rail), and `DataAuditor` are deprecated as of 0.3 and emit a `DeprecationWarning` when constructed. They remain importable and will be removed in a later release. Tabular anomaly detection on dependency snapshots belongs in [PyOD](https://github.com/yzhao062/pyod).
409
+
410
+ ## Related Projects
411
+
412
+ - [CatchBench](https://github.com/yzhao062/catchbench): the paper and benchmark whose board the leaderboards reproduce (arXiv:2608.22808).
413
+ - [agentpile](https://github.com/yzhao062/agentpile): the converter that loads public agent-trace datasets, trace files and captured runs into one record, which `from_agentpile` reads.
414
+ - [GRADE](https://arxiv.org/abs/2606.22741): the typed two-layer graph of agent execution and dependency that the structural features read.
415
+ - [awesome-auditable-ai](https://github.com/yzhao062/awesome-auditable-ai): a reading list on AI agent reliability and auditing.
416
+
417
+ ## Citation
418
+
419
+ If you use `auditable`, please cite Auditable Agents:
420
+
421
+ ```bibtex
422
+ @misc{nian2026auditable,
423
+ title = {Auditable Agents},
424
+ author = {Nian, Yi and Yuan, Aojie and Zhang, Haiyue and Li, Jiate and Li, Li and Hu, Xiyang and Wei, Hua and Xiao, Xiongye and Xiao, Chaowei and Zhao, Yue},
425
+ year = {2026},
426
+ eprint = {2604.05485},
427
+ archivePrefix = {arXiv}
428
+ }
429
+ ```
430
+
431
+ If you use the structural features or the benchmark, please also cite GRADE and CatchBench, and for each method, the work behind it (its `MethodCard.cite`):
432
+
433
+ ```bibtex
434
+ @article{zhao2026grade,
435
+ title = {GRADE: Graph Representation of LLM Agent Dependency and Execution},
436
+ author = {Zhao, Yue},
437
+ journal = {arXiv preprint arXiv:2606.22741},
438
+ year = {2026}
439
+ }
440
+
441
+ @misc{zhao2026catchbench,
442
+ title = {{CatchBench}: When Can an Agent Failure Be Caught?},
443
+ author = {Yue Zhao},
444
+ year = {2026},
445
+ eprint = {2608.22808},
446
+ archivePrefix = {arXiv}
447
+ }
448
+ ```
449
+
450
+ ## License
451
+
452
+ [Apache-2.0](https://github.com/yzhao062/auditable/blob/main/LICENSE). Ported code keeps the notice of its source (GRADE and CatchBench, both MIT License) at the top of each module.