tracefork 0.2.0__tar.gz → 0.2.1__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 (137) hide show
  1. {tracefork-0.2.0 → tracefork-0.2.1}/CHANGELOG.md +40 -1
  2. {tracefork-0.2.0 → tracefork-0.2.1}/CLAUDE.md +14 -9
  3. {tracefork-0.2.0 → tracefork-0.2.1}/PKG-INFO +55 -8
  4. {tracefork-0.2.0 → tracefork-0.2.1}/README.md +40 -3
  5. tracefork-0.2.1/docs/packaging-decisions.md +170 -0
  6. {tracefork-0.2.0 → tracefork-0.2.1}/pyproject.toml +38 -10
  7. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/__init__.py +12 -0
  8. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/adapters/__init__.py +19 -3
  9. tracefork-0.2.1/src/tracefork/adapters/adk.py +465 -0
  10. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/adapters/autogen.py +11 -3
  11. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/adapters/crewai.py +11 -3
  12. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/adapters/langchain.py +22 -6
  13. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/adapters/openai_agents.py +11 -3
  14. tracefork-0.2.1/tests/test_adapters_adk.py +305 -0
  15. {tracefork-0.2.0 → tracefork-0.2.1}/tests/test_adapters_autogen.py +2 -1
  16. {tracefork-0.2.0 → tracefork-0.2.1}/tests/test_adapters_crewai.py +2 -1
  17. {tracefork-0.2.0 → tracefork-0.2.1}/tests/test_adapters_langchain.py +4 -2
  18. {tracefork-0.2.0 → tracefork-0.2.1}/tests/test_adapters_openai_agents.py +2 -1
  19. {tracefork-0.2.0 → tracefork-0.2.1}/uv.lock +141 -6
  20. {tracefork-0.2.0 → tracefork-0.2.1}/.editorconfig +0 -0
  21. {tracefork-0.2.0 → tracefork-0.2.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  22. {tracefork-0.2.0 → tracefork-0.2.1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  23. {tracefork-0.2.0 → tracefork-0.2.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  24. {tracefork-0.2.0 → tracefork-0.2.1}/.github/dependabot.yml +0 -0
  25. {tracefork-0.2.0 → tracefork-0.2.1}/.github/pull_request_template.md +0 -0
  26. {tracefork-0.2.0 → tracefork-0.2.1}/.github/workflows/ci.yml +0 -0
  27. {tracefork-0.2.0 → tracefork-0.2.1}/.github/workflows/release.yml +0 -0
  28. {tracefork-0.2.0 → tracefork-0.2.1}/.gitignore +0 -0
  29. {tracefork-0.2.0 → tracefork-0.2.1}/.pre-commit-config.yaml +0 -0
  30. {tracefork-0.2.0 → tracefork-0.2.1}/CODE_OF_CONDUCT.md +0 -0
  31. {tracefork-0.2.0 → tracefork-0.2.1}/CONTRIBUTING.md +0 -0
  32. {tracefork-0.2.0 → tracefork-0.2.1}/LICENSE +0 -0
  33. {tracefork-0.2.0 → tracefork-0.2.1}/SECURITY.md +0 -0
  34. {tracefork-0.2.0 → tracefork-0.2.1}/SPIKE0.md +0 -0
  35. {tracefork-0.2.0 → tracefork-0.2.1}/docs/demo.png +0 -0
  36. {tracefork-0.2.0 → tracefork-0.2.1}/examples/demo_report.py +0 -0
  37. {tracefork-0.2.0 → tracefork-0.2.1}/experiments/replay_fixtures/manifest.json +0 -0
  38. {tracefork-0.2.0 → tracefork-0.2.1}/experiments/replay_fixtures/single_turn.tape.sqlite +0 -0
  39. {tracefork-0.2.0 → tracefork-0.2.1}/experiments/replay_fixtures/two_turn.tape.sqlite +0 -0
  40. {tracefork-0.2.0 → tracefork-0.2.1}/experiments/validation_report_committed.json +0 -0
  41. {tracefork-0.2.0 → tracefork-0.2.1}/scripts/e2e.sh +0 -0
  42. {tracefork-0.2.0 → tracefork-0.2.1}/scripts/gen_replay_fixtures.py +0 -0
  43. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/adapters/base.py +0 -0
  44. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/bedrock_transport.py +0 -0
  45. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/bench.py +0 -0
  46. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/blame.py +0 -0
  47. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/boundary_guard.py +0 -0
  48. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/cli.py +0 -0
  49. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/competing_faults.py +0 -0
  50. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/config.py +0 -0
  51. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/constants.py +0 -0
  52. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/data/pricing.json +0 -0
  53. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/divergence.py +0 -0
  54. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/eventstream.py +0 -0
  55. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/faults.py +0 -0
  56. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/fixtures.py +0 -0
  57. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/fork.py +0 -0
  58. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/interop.py +0 -0
  59. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/judge.py +0 -0
  60. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/matcher.py +0 -0
  61. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/mcp_client.py +0 -0
  62. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/nondet.py +0 -0
  63. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/observability.py +0 -0
  64. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/plugins.py +0 -0
  65. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/pricing.py +0 -0
  66. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/providers/__init__.py +0 -0
  67. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/providers/anthropic.py +0 -0
  68. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/providers/base.py +0 -0
  69. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/providers/bedrock.py +0 -0
  70. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/providers/gemini.py +0 -0
  71. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/providers/openai.py +0 -0
  72. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/proxy.py +0 -0
  73. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/py.typed +0 -0
  74. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/record_mode.py +0 -0
  75. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/recorder.py +0 -0
  76. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/redact.py +0 -0
  77. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/replay.py +0 -0
  78. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/report.py +0 -0
  79. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/server.py +0 -0
  80. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/store.py +0 -0
  81. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/synthetic.py +0 -0
  82. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/tape.py +0 -0
  83. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/tools.py +0 -0
  84. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/transport.py +0 -0
  85. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/validate.py +0 -0
  86. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork/wire.py +0 -0
  87. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork_spike/__init__.py +0 -0
  88. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork_spike/__main__.py +0 -0
  89. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork_spike/agent.py +0 -0
  90. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork_spike/fake_llm.py +0 -0
  91. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork_spike/nondet.py +0 -0
  92. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork_spike/spike.py +0 -0
  93. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork_spike/tape.py +0 -0
  94. {tracefork-0.2.0 → tracefork-0.2.1}/src/tracefork_spike/transport.py +0 -0
  95. {tracefork-0.2.0 → tracefork-0.2.1}/tests/__init__.py +0 -0
  96. {tracefork-0.2.0 → tracefork-0.2.1}/tests/fakes.py +0 -0
  97. {tracefork-0.2.0 → tracefork-0.2.1}/tests/fixtures/legacy_tape_v1.blob +0 -0
  98. {tracefork-0.2.0 → tracefork-0.2.1}/tests/test_adapters_base.py +0 -0
  99. {tracefork-0.2.0 → tracefork-0.2.1}/tests/test_bedrock_eventstream.py +0 -0
  100. {tracefork-0.2.0 → tracefork-0.2.1}/tests/test_bedrock_provider.py +0 -0
  101. {tracefork-0.2.0 → tracefork-0.2.1}/tests/test_bedrock_transport.py +0 -0
  102. {tracefork-0.2.0 → tracefork-0.2.1}/tests/test_bench.py +0 -0
  103. {tracefork-0.2.0 → tracefork-0.2.1}/tests/test_blame.py +0 -0
  104. {tracefork-0.2.0 → tracefork-0.2.1}/tests/test_boundary_guard.py +0 -0
  105. {tracefork-0.2.0 → tracefork-0.2.1}/tests/test_cli.py +0 -0
  106. {tracefork-0.2.0 → tracefork-0.2.1}/tests/test_cli_smoke.py +0 -0
  107. {tracefork-0.2.0 → tracefork-0.2.1}/tests/test_competing_faults.py +0 -0
  108. {tracefork-0.2.0 → tracefork-0.2.1}/tests/test_concurrency.py +0 -0
  109. {tracefork-0.2.0 → tracefork-0.2.1}/tests/test_config.py +0 -0
  110. {tracefork-0.2.0 → tracefork-0.2.1}/tests/test_divergence.py +0 -0
  111. {tracefork-0.2.0 → tracefork-0.2.1}/tests/test_e2e.py +0 -0
  112. {tracefork-0.2.0 → tracefork-0.2.1}/tests/test_faults.py +0 -0
  113. {tracefork-0.2.0 → tracefork-0.2.1}/tests/test_fork.py +0 -0
  114. {tracefork-0.2.0 → tracefork-0.2.1}/tests/test_gemini_provider.py +0 -0
  115. {tracefork-0.2.0 → tracefork-0.2.1}/tests/test_interop.py +0 -0
  116. {tracefork-0.2.0 → tracefork-0.2.1}/tests/test_judge.py +0 -0
  117. {tracefork-0.2.0 → tracefork-0.2.1}/tests/test_matcher.py +0 -0
  118. {tracefork-0.2.0 → tracefork-0.2.1}/tests/test_mcp_client.py +0 -0
  119. {tracefork-0.2.0 → tracefork-0.2.1}/tests/test_nondet.py +0 -0
  120. {tracefork-0.2.0 → tracefork-0.2.1}/tests/test_observability.py +0 -0
  121. {tracefork-0.2.0 → tracefork-0.2.1}/tests/test_openai_provider.py +0 -0
  122. {tracefork-0.2.0 → tracefork-0.2.1}/tests/test_plugins.py +0 -0
  123. {tracefork-0.2.0 → tracefork-0.2.1}/tests/test_pricing.py +0 -0
  124. {tracefork-0.2.0 → tracefork-0.2.1}/tests/test_provider_faults.py +0 -0
  125. {tracefork-0.2.0 → tracefork-0.2.1}/tests/test_providers.py +0 -0
  126. {tracefork-0.2.0 → tracefork-0.2.1}/tests/test_proxy.py +0 -0
  127. {tracefork-0.2.0 → tracefork-0.2.1}/tests/test_record_mode.py +0 -0
  128. {tracefork-0.2.0 → tracefork-0.2.1}/tests/test_recorder.py +0 -0
  129. {tracefork-0.2.0 → tracefork-0.2.1}/tests/test_redact.py +0 -0
  130. {tracefork-0.2.0 → tracefork-0.2.1}/tests/test_replay.py +0 -0
  131. {tracefork-0.2.0 → tracefork-0.2.1}/tests/test_report.py +0 -0
  132. {tracefork-0.2.0 → tracefork-0.2.1}/tests/test_spike0.py +0 -0
  133. {tracefork-0.2.0 → tracefork-0.2.1}/tests/test_storage.py +0 -0
  134. {tracefork-0.2.0 → tracefork-0.2.1}/tests/test_tape.py +0 -0
  135. {tracefork-0.2.0 → tracefork-0.2.1}/tests/test_tools.py +0 -0
  136. {tracefork-0.2.0 → tracefork-0.2.1}/tests/test_transport.py +0 -0
  137. {tracefork-0.2.0 → tracefork-0.2.1}/web/report.html +0 -0
@@ -7,6 +7,44 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.1] - 2026-07-03
11
+
12
+ ### Added
13
+
14
+ - **Google ADK adapter** (`adapters/adk.py`, opt-in `adk` extra) — `bind()` routes
15
+ an ADK `LlmAgent`/`Gemini`'s underlying `google-genai` client through the
16
+ existing `TraceforkTransport` via a short candidate-path search for the
17
+ `google.genai` `BaseApiClient`'s private `_httpx_client`/`_async_httpx_client`
18
+ attributes (the target itself, a `genai.Client`, an ADK `Gemini` model wrapper,
19
+ or an `LlmAgent` whose `.model` already holds one) — the same Gemini
20
+ `generateContent` wire format `providers/gemini.py` already parses. Step
21
+ visibility is a real `BasePlugin` (`make_plugin()`) over ADK's documented
22
+ agent/model/tool before/after callback boundaries, registered once on the
23
+ `Runner` rather than threaded through every agent — observer-only, never a
24
+ second capture path.
25
+ - **Curated `all` extra** (`pip install 'tracefork[all]'`) — a self-referential
26
+ convenience bundle over `providers` + `bedrock` + `mcp` + `observability` (the
27
+ internally-consistent, stable-wire family). Deliberately excludes the five
28
+ independently-capped, fast-moving framework stacks (`frameworks`,
29
+ `openai-agents`, `crewai`, `autogen`, `adk`) so one future cap collision on any
30
+ single framework can't `ResolutionImpossible` the whole `all` install.
31
+
32
+ ### Changed
33
+
34
+ - **Relaxed the `frameworks` extra's version caps to floors** — dropped the
35
+ speculative `<2` upper bounds on `langchain-core`/`langchain-openai`/
36
+ `langchain-anthropic`/`langgraph`. LangChain 1.0 GA commits to no breaking
37
+ changes until 2.0, so a `<2` cap was speculative and, more importantly, useless
38
+ against the real observed failure mode: intra-1.x patch regressions ship
39
+ *inside* the allowed range regardless of the cap. The remaining internal-
40
+ coupling caps (`openai-agents`, `crewai`, `autogen`, `adk` — each an adapter
41
+ injecting into private/undocumented framework internals) are kept, each now
42
+ with an inline "hint, not a guard" rationale and a revisit TODO.
43
+ - **Adapter import guards now chain the root cause** (`raise ImportError(HINT)
44
+ from exc`) in `adapters/{langchain,openai_agents,crewai,autogen,adk}.py`'s
45
+ `require_*()` functions, so an installed-but-broken dependency's real
46
+ `ImportError` is preserved instead of being masked as "not installed".
47
+
10
48
  ## [0.2.0] - 2026-07-02
11
49
 
12
50
  Generic, multi-provider, production-hardening release. The bit-exact, $0,
@@ -119,6 +157,7 @@ everything below is additive around it: all engine internals stay byte-stable,
119
157
  - `src/tracefork_spike/` — the original Spike 0 that de-risked bit-exact, no-key replay
120
158
  within the declared determinism boundary.
121
159
 
122
- [Unreleased]: https://github.com/pratik916/tracefork/compare/v0.2.0...HEAD
160
+ [Unreleased]: https://github.com/pratik916/tracefork/compare/v0.2.1...HEAD
161
+ [0.2.1]: https://github.com/pratik916/tracefork/compare/v0.2.0...v0.2.1
123
162
  [0.2.0]: https://github.com/pratik916/tracefork/compare/v0.1.0...v0.2.0
124
163
  [0.1.0]: https://github.com/pratik916/tracefork/releases/tag/v0.1.0
@@ -10,7 +10,7 @@ step, and measure causal blame with confidence intervals — the instrument itse
10
10
  validated against runs with injected, known root-cause faults.
11
11
 
12
12
  **Current state: v1 built.** All five product pillars work offline and are tested
13
- (653 tests, $0): streaming-capable record/replay with drift detection, the three-phase
13
+ (672 tests, $0): streaming-capable record/replay with drift detection, the three-phase
14
14
  fork engine, the causal blame engine with Wilson CIs and a budget governor, the
15
15
  single-file web report/UI, and the fault-injection self-validation suite (5 fault
16
16
  classes at 1.00 top-1 precision, plus a longer competing-fault fixture that measures
@@ -29,7 +29,7 @@ record/replay/fork are offline and $0 — **no `ANTHROPIC_API_KEY`, no network**
29
29
 
30
30
  ```bash
31
31
  uv sync --extra dev # install (anthropic, zstandard, typer, fastapi, uvicorn + pytest)
32
- uv run pytest -q # full offline suite (653 tests)
32
+ uv run pytest -q # full offline suite (672 tests)
33
33
  uv run pytest tests/test_faults.py::test_validation_runner_fingers_fault_step -q # one test
34
34
  uv run tracefork validate # self-validation: blame vs injected, known faults
35
35
  uv run tracefork validate --check # regression-gate vs experiments/validation_report_committed.json
@@ -175,13 +175,18 @@ The product lives in `src/tracefork/`:
175
175
  checkpointer), `openai_agents.py` (defensive client-attribute injection +
176
176
  `agents.set_default_openai_client()` + a `TracingProcessor`),
177
177
  `crewai.py` (LiteLLM's `client_session`/`aclient_session` — CrewAI's actual httpx
178
- chokepoint — + a `crewai_event_bus` listener), and `autogen.py` (defensive
179
- client-attribute injection + an `InterventionHandler` message-level seam) are the
180
- concrete adapters, each its own optional extra with every framework import
181
- guarded, so `import tracefork` and the whole offline suite work with none of
182
- them installed. Where a framework's exact internal attribute names/event
183
- shapes aren't a documented stable API, injection is defensive (a short
184
- candidate list, never one hard-coded name) see each module's docstring.
178
+ chokepoint — + a `crewai_event_bus` listener), `autogen.py` (defensive
179
+ client-attribute injection + an `InterventionHandler` message-level seam), and
180
+ `adk.py` (Google ADK: candidate-path injection into the `google-genai`
181
+ `BaseApiClient`'s private `_httpx_client`/`_async_httpx_client` reached
182
+ through the target itself, a `genai.Client`, an ADK `Gemini` model wrapper, or
183
+ an `LlmAgent` — + a `BasePlugin` registered once on the `Runner` for
184
+ agent/model/tool before/after boundaries) are the concrete adapters, each its
185
+ own optional extra with every framework import guarded, so `import tracefork`
186
+ and the whole offline suite work with none of them installed. Where a
187
+ framework's exact internal attribute names/event shapes aren't a documented
188
+ stable API, injection is defensive (a short candidate list, never one
189
+ hard-coded name) — see each module's docstring.
185
190
  - `cli.py` — Typer entry point for all eleven commands.
186
191
 
187
192
  `src/tracefork_spike/` holds the original Spike 0 (`fake_llm.py`, `agent.py`, `spike.py`):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tracefork
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Time-travel debugger for AI agents: bit-exact record/replay, fork any step, causal blame with confidence intervals.
5
5
  Project-URL: Homepage, https://github.com/pratik916/tracefork
6
6
  Project-URL: Repository, https://github.com/pratik916/tracefork
@@ -27,6 +27,16 @@ Requires-Dist: fastapi>=0.115
27
27
  Requires-Dist: typer>=0.12
28
28
  Requires-Dist: uvicorn>=0.30
29
29
  Requires-Dist: zstandard>=0.22
30
+ Provides-Extra: adk
31
+ Requires-Dist: google-adk<3,>=2.3; extra == 'adk'
32
+ Provides-Extra: all
33
+ Requires-Dist: boto3>=1.34; extra == 'all'
34
+ Requires-Dist: google-genai>=0.3; extra == 'all'
35
+ Requires-Dist: mcp>=1.0; extra == 'all'
36
+ Requires-Dist: openai>=1.0; extra == 'all'
37
+ Requires-Dist: opentelemetry-api>=1.27; extra == 'all'
38
+ Requires-Dist: opentelemetry-sdk>=1.27; extra == 'all'
39
+ Requires-Dist: structlog>=24.1; extra == 'all'
30
40
  Provides-Extra: autogen
31
41
  Requires-Dist: autogen-core<1,>=0.7; extra == 'autogen'
32
42
  Requires-Dist: autogen-ext<1,>=0.7; extra == 'autogen'
@@ -41,10 +51,10 @@ Requires-Dist: pytest-cov>=5; extra == 'dev'
41
51
  Requires-Dist: pytest>=8; extra == 'dev'
42
52
  Requires-Dist: ruff>=0.6; extra == 'dev'
43
53
  Provides-Extra: frameworks
44
- Requires-Dist: langchain-anthropic<2,>=1.4; extra == 'frameworks'
45
- Requires-Dist: langchain-core<2,>=1.4; extra == 'frameworks'
46
- Requires-Dist: langchain-openai<2,>=1.3; extra == 'frameworks'
47
- Requires-Dist: langgraph<2,>=1.2; extra == 'frameworks'
54
+ Requires-Dist: langchain-anthropic>=1.4; extra == 'frameworks'
55
+ Requires-Dist: langchain-core>=1.4; extra == 'frameworks'
56
+ Requires-Dist: langchain-openai>=1.3; extra == 'frameworks'
57
+ Requires-Dist: langgraph>=1.2; extra == 'frameworks'
48
58
  Provides-Extra: mcp
49
59
  Requires-Dist: mcp>=1.0; extra == 'mcp'
50
60
  Provides-Extra: observability
@@ -123,7 +133,7 @@ makes no network calls.
123
133
  ```bash
124
134
  uv sync --extra dev
125
135
 
126
- # 1. The full offline test suite (653 tests) -- including a cross-module,
136
+ # 1. The full offline test suite (672 tests) -- including a cross-module,
127
137
  # whole-pipeline suite (tests/test_e2e.py) and an all-CLI-commands smoke
128
138
  # test (tests/test_cli_smoke.py), not just per-module unit tests.
129
139
  uv run pytest -q
@@ -164,6 +174,34 @@ bash scripts/e2e.sh
164
174
  negative control max flip: 0.00 (threshold 0.30)
165
175
  ```
166
176
 
177
+ ## Optional extras
178
+
179
+ The core install (`pip install tracefork`) is offline/$0 and pulls in no framework or
180
+ provider SDKs. Everything else — providers, Bedrock, MCP, observability, and each
181
+ framework adapter — is opt-in via an extra. A curated bundle of the internally-
182
+ consistent, stable-wire family is available in one shot:
183
+
184
+ ```bash
185
+ pip install 'tracefork[all]'
186
+ ```
187
+
188
+ | Extra | Installs | Note |
189
+ |---|---|---|
190
+ | `all` | `providers` + `bedrock` + `mcp` + `observability` | Convenience bundle. Deliberately **excludes** the framework stacks (`frameworks`, `openai-agents`, `crewai`, `autogen`, `adk`) so one framework's future version cap can't block installing everything else. |
191
+ | `providers` | `openai`, `google-genai` SDKs | Record/replay against OpenAI or Gemini directly — not the same as `openai-agents` below. |
192
+ | `bedrock` | `boto3` | AWS Bedrock record/replay. |
193
+ | `mcp` | `mcp` | Model Context Protocol client record/replay. |
194
+ | `observability` | `structlog`, `opentelemetry-*` | Self-instrumentation logging/tracing of tracefork itself. |
195
+ | `frameworks` | `langchain-core`, `langchain-openai`, `langchain-anthropic`, `langgraph` | LangChain/LangGraph adapter. |
196
+ | `openai-agents` | `openai-agents` (the OpenAI **Agents SDK**) | Not the plain `openai` SDK — that's `providers`. |
197
+ | `crewai` | `crewai` | CrewAI adapter (routes through LiteLLM). |
198
+ | `autogen` | `autogen-core`, `autogen-ext` | AutoGen adapter. |
199
+ | `adk` | `google-adk` (Google **Agent Development Kit**) | An agent framework, not a provider SDK. |
200
+ | `dev` | `pytest`, `ruff`, `mypy`, ... | Local development/test tooling. |
201
+
202
+ Every bracketed install command in this README is single-quoted — unquoted `[...]`
203
+ gets glob-expanded by zsh (macOS's default shell) into `no matches found`.
204
+
167
205
  ## The CLI
168
206
 
169
207
  ```bash
@@ -365,7 +403,7 @@ exercised against the real library when present and skipped cleanly otherwise, s
365
403
  the pinned adapter version ranges (which churn) are validated separately from the
366
404
  offline core.
367
405
 
368
- Three more adapters ship the same way, each its own optional extra and each
406
+ Four more adapters ship the same way, each its own optional extra and each
369
407
  targeting the framework's actual model-call chokepoint:
370
408
 
371
409
  - **OpenAI Agents SDK** (`pip install 'tracefork[openai-agents]'`) — `bind()` injects
@@ -384,6 +422,15 @@ targeting the framework's actual model-call chokepoint:
384
422
  defensive attribute search). Step visibility is a message-level `InterventionHandler`
385
423
  (`make_intervention_handler()`) — pass-through only, so it stays an annotation
386
424
  layer, never a second capture path.
425
+ - **Google ADK** (`pip install 'tracefork[adk]'`, Agent Development Kit) — ADK's
426
+ model calls go through the `google-genai` SDK, so `bind()` walks a short list of
427
+ candidate attribute paths (the target itself, a `genai.Client`, an ADK `Gemini`
428
+ model wrapper, or an `LlmAgent` whose `.model` already holds one) to find the
429
+ `google.genai` `BaseApiClient` and swap in tracefork's httpx clients — the same
430
+ Gemini `generateContent` wire format `providers/gemini.py` already parses. Step
431
+ visibility is a real `BasePlugin` (`make_plugin()`, installable via
432
+ `Runner(..., plugins=[plugin])`) over agent/model/tool before/after boundaries —
433
+ registered once for the whole run rather than threaded through every `LlmAgent`.
387
434
 
388
435
  Each adapter's real-framework wrapper is import-guarded and validated against a
389
436
  synthetic stand-in mimicking the framework's interface (never a live call) in the
@@ -549,7 +596,7 @@ src/tracefork/ transport, tape, nondet, recorder, matcher, redact, fork, st
549
596
  interop (OTel GenAI / OpenInference export+ingest),
550
597
  observability (opt-in structlog + OTel self-instrumentation),
551
598
  adapters/ (opt-in framework seam: LangChain/LangGraph, OpenAI
552
- Agents SDK, CrewAI, AutoGen),
599
+ Agents SDK, CrewAI, AutoGen, Google ADK),
553
600
  bedrock_transport (opt-in botocore before-send record/replay seam),
554
601
  eventstream (standalone AWS event-stream binary framing codec),
555
602
  proxy (opt-in localhost base-URL record/replay proxy for non-Python clients),
@@ -63,7 +63,7 @@ makes no network calls.
63
63
  ```bash
64
64
  uv sync --extra dev
65
65
 
66
- # 1. The full offline test suite (653 tests) -- including a cross-module,
66
+ # 1. The full offline test suite (672 tests) -- including a cross-module,
67
67
  # whole-pipeline suite (tests/test_e2e.py) and an all-CLI-commands smoke
68
68
  # test (tests/test_cli_smoke.py), not just per-module unit tests.
69
69
  uv run pytest -q
@@ -104,6 +104,34 @@ bash scripts/e2e.sh
104
104
  negative control max flip: 0.00 (threshold 0.30)
105
105
  ```
106
106
 
107
+ ## Optional extras
108
+
109
+ The core install (`pip install tracefork`) is offline/$0 and pulls in no framework or
110
+ provider SDKs. Everything else — providers, Bedrock, MCP, observability, and each
111
+ framework adapter — is opt-in via an extra. A curated bundle of the internally-
112
+ consistent, stable-wire family is available in one shot:
113
+
114
+ ```bash
115
+ pip install 'tracefork[all]'
116
+ ```
117
+
118
+ | Extra | Installs | Note |
119
+ |---|---|---|
120
+ | `all` | `providers` + `bedrock` + `mcp` + `observability` | Convenience bundle. Deliberately **excludes** the framework stacks (`frameworks`, `openai-agents`, `crewai`, `autogen`, `adk`) so one framework's future version cap can't block installing everything else. |
121
+ | `providers` | `openai`, `google-genai` SDKs | Record/replay against OpenAI or Gemini directly — not the same as `openai-agents` below. |
122
+ | `bedrock` | `boto3` | AWS Bedrock record/replay. |
123
+ | `mcp` | `mcp` | Model Context Protocol client record/replay. |
124
+ | `observability` | `structlog`, `opentelemetry-*` | Self-instrumentation logging/tracing of tracefork itself. |
125
+ | `frameworks` | `langchain-core`, `langchain-openai`, `langchain-anthropic`, `langgraph` | LangChain/LangGraph adapter. |
126
+ | `openai-agents` | `openai-agents` (the OpenAI **Agents SDK**) | Not the plain `openai` SDK — that's `providers`. |
127
+ | `crewai` | `crewai` | CrewAI adapter (routes through LiteLLM). |
128
+ | `autogen` | `autogen-core`, `autogen-ext` | AutoGen adapter. |
129
+ | `adk` | `google-adk` (Google **Agent Development Kit**) | An agent framework, not a provider SDK. |
130
+ | `dev` | `pytest`, `ruff`, `mypy`, ... | Local development/test tooling. |
131
+
132
+ Every bracketed install command in this README is single-quoted — unquoted `[...]`
133
+ gets glob-expanded by zsh (macOS's default shell) into `no matches found`.
134
+
107
135
  ## The CLI
108
136
 
109
137
  ```bash
@@ -305,7 +333,7 @@ exercised against the real library when present and skipped cleanly otherwise, s
305
333
  the pinned adapter version ranges (which churn) are validated separately from the
306
334
  offline core.
307
335
 
308
- Three more adapters ship the same way, each its own optional extra and each
336
+ Four more adapters ship the same way, each its own optional extra and each
309
337
  targeting the framework's actual model-call chokepoint:
310
338
 
311
339
  - **OpenAI Agents SDK** (`pip install 'tracefork[openai-agents]'`) — `bind()` injects
@@ -324,6 +352,15 @@ targeting the framework's actual model-call chokepoint:
324
352
  defensive attribute search). Step visibility is a message-level `InterventionHandler`
325
353
  (`make_intervention_handler()`) — pass-through only, so it stays an annotation
326
354
  layer, never a second capture path.
355
+ - **Google ADK** (`pip install 'tracefork[adk]'`, Agent Development Kit) — ADK's
356
+ model calls go through the `google-genai` SDK, so `bind()` walks a short list of
357
+ candidate attribute paths (the target itself, a `genai.Client`, an ADK `Gemini`
358
+ model wrapper, or an `LlmAgent` whose `.model` already holds one) to find the
359
+ `google.genai` `BaseApiClient` and swap in tracefork's httpx clients — the same
360
+ Gemini `generateContent` wire format `providers/gemini.py` already parses. Step
361
+ visibility is a real `BasePlugin` (`make_plugin()`, installable via
362
+ `Runner(..., plugins=[plugin])`) over agent/model/tool before/after boundaries —
363
+ registered once for the whole run rather than threaded through every `LlmAgent`.
327
364
 
328
365
  Each adapter's real-framework wrapper is import-guarded and validated against a
329
366
  synthetic stand-in mimicking the framework's interface (never a live call) in the
@@ -489,7 +526,7 @@ src/tracefork/ transport, tape, nondet, recorder, matcher, redact, fork, st
489
526
  interop (OTel GenAI / OpenInference export+ingest),
490
527
  observability (opt-in structlog + OTel self-instrumentation),
491
528
  adapters/ (opt-in framework seam: LangChain/LangGraph, OpenAI
492
- Agents SDK, CrewAI, AutoGen),
529
+ Agents SDK, CrewAI, AutoGen, Google ADK),
493
530
  bedrock_transport (opt-in botocore before-send record/replay seam),
494
531
  eventstream (standalone AWS event-stream binary framing codec),
495
532
  proxy (opt-in localhost base-URL record/replay proxy for non-Python clients),
@@ -0,0 +1,170 @@
1
+ # tracefork Packaging: Best Practices for Optional Framework Integrations
2
+
3
+ *Synthesis of six adversarially-vetted research dimensions on Python packaging for optional, extra-gated framework integrations. Every load-bearing claim below is tied to a primary source; where the underlying evidence was **DISPUTED** or **NUANCED**, that is called out inline rather than smoothed over.*
4
+
5
+ ---
6
+
7
+ ## 1. TL;DR verdict
8
+
9
+ - **Keep per-integration PEP 621 extras — they are mandatory, not stylistic.** PEP 735 dependency-groups are *structurally unreachable* via `pip install tracefork[adk]` (they are excluded from published wheel/sdist metadata and have no install interface), so extras are the only bracket-installable mechanism for user-facing integrations ([PEP 735, Final](https://peps.python.org/pep-0735/); [Dependency Groups spec](https://packaging.python.org/en/latest/specifications/dependency-groups/)). Do **not** split the ~9 thin, first-party, observer-only adapters into separate `tracefork-*` distributions — tracefork has none of the conditions (scale, decoupled cadence, distributed ownership) that drove the famous splits ([Airflow AIP-8](https://cwiki.apache.org/confluence/display/AIRFLOW/AIP-8+Split+Providers+into+Separate+Packages+for+Airflow+2.0); [LangChain v0.1](https://www.langchain.com/blog/langchain-v0-1-0)).
10
+ - **Add a *curated* `all` extra via a self-reference — but deliberately exclude the mutually-heavy framework stacks.** A single god-`all` over five independently-capped, fast-moving agent frameworks is a fragility and bloat anti-pattern ([PEP 771 bloat guidance](https://peps.python.org/pep-0771/)). Ship `all = ["tracefork[providers,bedrock,mcp,observability]"]` and leave the framework stacks to their own extras. Self-referential extras are well-supported and, under hatchling, flattened into concrete metadata at build time ([Hynek Schlawack](https://hynek.me/articles/python-recursive-optional-dependencies/); [core-metadata thread](https://discuss.python.org/t/core-metadata-for-self-referential-extras/77793)).
11
+ - **Drop the *speculative* `<2`/`<3` caps; keep caps only on genuinely churny/pre-stable deps, each with an inline rationale + revisit note.** In Python's flat graph a library upper-cap cannot be overridden downstream and is permanent once on PyPI ([Schreiner](https://iscinumpy.dev/post/bound-version-constraints/); [PyPA install_requires](https://packaging.python.org/en/latest/discussions/install-requires-vs-requirements/)). Replace the `langchain-core<2` cap with a floor + targeted `!=`; keep single-major caps on `openai-agents` (0.x), `autogen`, `crewai`, and `google-adk` **only** because their adapters inject into private/undocumented internals — Schreiner's blessed exception.
12
+ - **Upgrade the runtime guard to `raise ImportError(HINT) from exc`.** Keep the lazy `X_available()`/`require_X()` pair and the exact-extra-name hint; the one high-confidence, do-it-now change is preserving the chained cause so an *installed-but-broken* dependency's real error survives ([Real Python](https://realpython.com/python-raise-exception/); [Python exception chaining](https://docs.python.org/3/library/exceptions.html)).
13
+ - **Migrate `dev` to `[dependency-groups]` — but treat it as a deliberate, later, breaking change,** not a free win: it removes `uv sync --extra dev` and every call site in `CLAUDE.md`/`README`/`scripts/e2e.sh` must move to `--group dev` in the same commit ([PEP 735](https://peps.python.org/pep-0735/); [pip 25.1 `--group`](https://ichard26.github.io/blog/2025/04/whats-new-in-pip-25.1/)).
14
+
15
+ ---
16
+
17
+ ## 2. Per-dimension best practice
18
+
19
+ ### 2.1 Standards & mechanism (extras vs PEP 735 dependency-groups)
20
+
21
+ **Best practice: user-facing optional integrations MUST be `[project.optional-dependencies]` (extras); development-only tooling belongs in `[dependency-groups]`.** This is not a style preference — it is a structural constraint. The finalized spec states build backends **"MUST NOT include Dependency Group data in built distributions as package metadata"** and that **"there is no syntax or specification-defined interface for installing or referring to dependency groups"** ([Dependency Groups spec](https://packaging.python.org/en/latest/specifications/dependency-groups/); [PEP 735, Status: Final](https://peps.python.org/pep-0735/)). Extras, by contrast, are published to `METADATA` (`Provides-Extra`/`Requires-Dist`) and installable via `pkg[extra]`. pyOpenSci frames the division cleanly: optional-dependencies are for functionality "a user wants to access," dependency-groups "organize development dependencies and are intentionally separate" ([pyOpenSci](https://www.pyopensci.org/python-package-guide/package-structure-code/declare-dependencies.html)).
22
+
23
+ *Verdict: **SUPPORTED**, unqualified.* tracefork's langchain/crewai/adk/provider adapters are exactly the user-facing case, so they must stay extras. Do **not** design around [PEP 771 default-extras](https://peps.python.org/pep-0771/) — still **Draft** (round-2 discussion June 2025), targeting an unshipped Metadata-Version 2.5, with only unreviewed proof-of-concept branches. Keep the base install minimal and every integration strictly opt-in.
24
+
25
+ ### 2.2 The `[all]` convenience extra
26
+
27
+ **Best practice: a self-referential meta-extra is a legitimate, well-precedented pattern — but curate it to an internally-consistent family, don't god-`all` heavyweight independent stacks.** The self-reference (`all = ["tracefork[providers,mcp,...]"]`) is an ordinary PEP 508 requirement naming the project itself, not special grammar; setuptools discussion #3627 confirms "that is perfectly OK," and dask ships exactly this in production: `complete = ["dask[array,dataframe,distributed,diagnostics]", ...]` ([Hynek](https://hynek.me/articles/python-recursive-optional-dependencies/); [dask pyproject.toml](https://raw.githubusercontent.com/dask/dask/main/pyproject.toml); [setuptools #3627](https://github.com/pypa/setuptools/discussions/3627)).
28
+
29
+ **NUANCED — mechanism correction:** the common framing that "the backend passes metadata through unchanged; the pip-21.2 resolver does the recursion" is **backwards for hatchling**. Charlie Marsh's demonstration shows hatchling **FLATTENS** the self-reference into concrete `Requires-Dist` at *build* time (setuptools preserves it), so a hatchling wheel's METADATA already contains the concrete union and pip never performs the recursion ([core-metadata thread](https://discuss.python.org/t/core-metadata-for-self-referential-extras/77793)). The end result is equivalent, but declare the self-ref knowing the backend resolves it. Paul Moore's caveat in that thread — "extras are badly specified by the standards" — is why the post-edit resolution check is required, not optional.
30
+
31
+ **NUANCED — the god-`all` warning is directional, not a proven unsolvable build:** the provable downsides of unioning tracefork's five capped, fast-moving frameworks (langchain, crewai, autogen, adk, openai-agents) are (a) **union-fragility** — one future cap collision breaks the whole `all` install though each extra installs fine alone — and (b) **install/vuln bloat**, which PEP 771 explicitly warns to avoid ("carefully consider what is included... to avoid unnecessarily bloating installations") ([PEP 771](https://peps.python.org/pep-0771/)). The claim that these frameworks are "not co-resolvable today" is **unverified** and should not be asserted — modern resolvers (uv) are strong, and running a real solve would violate the offline/$0 invariant. State the fragility mechanism, not a certainty.
32
+
33
+ *Not-triggered caveats worth documenting:* the [hatch #1610](https://github.com/pypa/hatch/issues/1610) self-ref bug is real but scoped to the `hatch env` *manager* — tracefork uses `uv sync`/`uv run`, never `hatch env`, so it cannot bite; and uv's own historical self-ref bug ([astral-sh/uv #1987](https://github.com/astral-sh/uv/issues/1987)) is long fixed. Still, **verify `uv sync --extra all` resolves after the edit** — recursive extras have documented lock/compile edge cases ([pip-tools #2002](https://github.com/jazzband/pip-tools/issues/2002)).
34
+
35
+ ### 2.3 Extras vs separate packages
36
+
37
+ **Best practice: keep integrations as extras until an adapter independently needs decoupled release cadence, gains an external co-maintainer, or develops a conflicting hard dependency — never split on integration *count*.** The decisive precondition behind every famous split was **scale + independent cadence + dependency isolation**, not count. Airflow's AIP-8 split providers for release-cadence and distributed-expertise reasons and *explicitly named the cost*: **"in the long-term structure there are many more packages to maintain"** ([AIP-8](https://cwiki.apache.org/confluence/display/AIRFLOW/AIP-8+Split+Providers+into+Separate+Packages+for+Airflow+2.0)). LangChain split at **700+ integrations** to fix install headaches, enable per-package breaking-change versioning, and de-bloat an unstable core with CVE exposure ([LangChain v0.1](https://www.langchain.com/blog/langchain-v0-1-0); [v0.2](https://blog.langchain.com/langchain-v02-leap-to-stability/)).
38
+
39
+ **NUANCED — the load-bearing reason is scale/cadence, not "no external partners."** Airflow's own docs confirm many providers are **community-maintained**, so external ownership is a *common but not necessary* trigger ([apache-airflow-providers](https://airflow.apache.org/docs/apache-airflow-providers/)). tracefork lacks the scale (~9 vs 58–700) and any cadence-decoupling need regardless. At its profile — solo maintainer, guarded imports already containing blast radius to opt-in users — a split's benefits are near-zero and AIP-8's named cost (N repos, N pipelines, an N×N core-vs-adapter compatibility matrix) lands in full.
40
+
41
+ *Future-proofing:* register an `importlib.metadata` entry-point group (e.g. `tracefork.adapters`) resolved by `adapters/base.py`, so a *later* extraction of any single adapter is non-breaking.
42
+
43
+ ### 2.4 Naming / normalization
44
+
45
+ **Best practice: lowercase-hyphen extra names — which tracefork already uses — are PEP 685/PEP 503 conformant; no rename is required.** PEP 685 mandates that extras be compared and *written* normalized (`re.sub(r"[-_.]+", "-", name).lower()`) and that generators error if two extras collide ([PEP 685](https://peps.python.org/pep-0685/); [Name normalization](https://packaging.python.org/en/latest/specifications/name-normalization/)). Hatchling normalizes extra names by default ([Hatch metadata docs](https://hatch.pypa.io/latest/config/metadata/)). tracefork's ten names (`dev, providers, bedrock, mcp, frameworks, openai-agents, crewai, autogen, adk, observability`) are all-lowercase, already-normalized, and collision-free — the MUST-error branch cannot fire.
46
+
47
+ **Three framing corrections (none change the KEEP verdict):**
48
+ - The transparent underscore↔hyphen equivalence is a **post-685-tooling property only**. The [huggingface_hub #3029](https://github.com/huggingface/huggingface_hub/issues/3029) bug shows pip ≤ 22.0.2 *silently skipped* a mismatched `hf_transfer` extra; pip 25.0.1+ tolerates it. This "doubly matters" for a self-referential `all`: both the extra name and the bracket must normalize consistently.
49
+ - Hatchling's `allow-ambiguous-features` opt-out is **deprecated-but-still-present**, not removed — soften any "removed" wording. tracefork never sets it, so this is moot.
50
+ - "lowercase kebab-case mirroring the distribution name" is a *consequence of normalization plus community practice*, not a convention the Packaging User Guide prescribes ([writing-pyproject-toml](https://packaging.python.org/en/latest/guides/writing-pyproject-toml/) prescribes none).
51
+
52
+ **The real user-facing gotcha is the shell, not TOML.** Bare TOML keys allow dashes, so `openai-agents = [...]` needs no quotes ([TOML v1.0.0](https://toml.io/en/v1.0.0)) — only a **dot** would force quoting. But zsh (default macOS shell) globs `[...]`, so unquoted `pip install tracefork[dev]` fails with `zsh: no matches found` ([napari #2081](https://github.com/napari/napari/issues/2081); [Berton](https://lucaberton.medium.com/how-to-fix-zsh-no-matches-found-when-installing-python-packages-with-optional-dependencies-40aec463ff09)). **Always quote bracketed install specs in README/CLAUDE.md.**
53
+
54
+ ### 2.5 Version caps
55
+
56
+ **Best practice: floor everywhere + targeted `!=` for proven-bad releases; reserve blanket upper caps for genuinely unstable deps that *also* carry a documented lift-the-cap cadence.** The consensus (Brett Cannon via Schreiner, PyPA, pip docs) is robust: "set a floor... but otherwise don't cap the maximum version as you can't predict future compatibility," and crucially **"you can't fix an over-constraint"** — an over-cap is unfixable downstream while an under-constraint is a trivial user workaround ([Schreiner](https://iscinumpy.dev/post/bound-version-constraints/)). PyPA's install_requires guide is author-facing and squarely agrees: preemptive pinning is "overly-restrictive, and prevents the user from gaining the benefit of dependency upgrades" ([PyPA](https://packaging.python.org/en/latest/discussions/install-requires-vs-requirements/)). Because PyPI artifacts are immutable, a bad cap in a shipped release is **permanent** ([prefix.dev](https://prefix.dev/blog/the_python_packaging_debate); [ResolutionImpossible](https://pip.pypa.io/en/stable/ux-research-design/resolution-impossible-example/)).
57
+
58
+ **Schreiner's blessed exceptions apply to tracefork's adapters — this is genuinely NUANCED, and the nuance *sharpens* the recommendation:**
59
+ - Caps are acceptable for "an extension for an ecosystem/framework (pytest/Sphinx/Jupyter extension)... capping on the major version" and when "you depend on private internal details of a library." tracefork's defensive-attribute-injection adapters (langchain/openai-agents/crewai/autogen/adk) fit both.
60
+ - **But a single-major cap gives ZERO protection against the real observed failure mode.** Schreiner's own words: private-internal breakage "can be broken in a minor or patch release, and often is." Verified in the wild: `langchain-core` shipped a 1.x patch regression breaking `ToolsRenderer`/`RetrieverInput` imports, and `langgraph-prebuilt` shipped breaking changes in patch releases **1.0.2** (required a new `runtime` param — [#6363](https://github.com/langchain-ai/langgraph/issues/6363)), **1.0.5** ([#6477](https://github.com/langchain-ai/langgraph/issues/6477)), and **1.0.9**. A `<2` cap catches *none* of these — they live inside 1.x. **Only a targeted `!=` catches them, and CI-against-latest is the actual guard; the cap is a courtesy hint.**
61
+
62
+ **Per-dependency calls:**
63
+ - **`langchain-core >=1.4,<2` → DROP to `>=1.4`.** LangChain 1.0 GA (Oct 22 2025) commits to "no breaking changes until 2.0" ([release policy](https://docs.langchain.com/oss/python/release-policy)), so `<2` is speculative *and* useless against the real intra-1.x breakage — keep `!=` in the toolkit, monitor.
64
+ - **`autogen-* >=0.7,<1` → KEEP.** Genuinely experimental/pre-stable: 0.4 is a ground-up async rewrite, officially "expect changes and bugs," now fracturing into Microsoft's Agent Framework ([migration guide](https://microsoft.github.io/autogen/0.4.8//user-guide/agentchat-user-guide/migration-guide.html)).
65
+ - **`google-adk >=2.3,<3` → KEEP, but correct the rationale.** ADK is **not** pre-1.0 — v1.0.0 shipped May 2025 as "stable, production-ready." The cap rests on **fast major cadence + adapter-internal coupling**, not "pre-1.0 instability," and is only honest paired with a CI item that tests the newest major each release.
66
+ - **`openai-agents >=0.10,<1` → KEEP** (0.x, pre-stable API, defensive internal injection).
67
+ - **`crewai >=1.0,<2` → KEEP defensibly** (adapter hooks LiteLLM's private `client_session` internals) *with* a revisit note.
68
+ - **`providers` (openai, google-genai), `bedrock` (boto3), `mcp`, `observability` (structlog, opentelemetry-*) → floors already; leave uncapped.** Stable-wire-format / self-instrumentation deps, not private-internal framework extensions.
69
+
70
+ *Caveat:* tracefork uses hatchling/PEP 621, so it never *inherits* Poetry-style auto-caps ([poetry-relax](https://pypi.org/project/poetry-relax/); [poetry #2731](https://github.com/python-poetry/poetry/issues/2731)) — these caps are hand-written and deliberate, which is exactly why each needs an inline `# TODO: widen` comment.
71
+
72
+ ### 2.6 Runtime UX
73
+
74
+ **Best practice: keep the lazy `X_available()` + `require_X()` guard pair naming the exact project extra; upgrade to chain the cause with `from exc`.** The idiom — import lazily, catch `ImportError`, re-raise a *new* `ImportError` naming the remediation — is confirmed against scikit-learn's `check_matplotlib_support`: `raise ImportError(f"... You can install matplotlib with 'pip install matplotlib'") from e` ([sklearn](https://github.com/scikit-learn/scikit-learn/blob/main/sklearn/utils/_optional_dependencies.py)). **DISPUTED exemplar:** sklearn is *inconsistent* — its sibling `check_pandas_support` raises only `"... requires pandas."` with no remediation. This **strengthens** tracefork's design: naming the exact quoted extra in *every* adapter is strictly better than sklearn's mixed messages.
75
+
76
+ `raise ... from exc` is the documented default for wrapping/transforming exceptions and preserves the original traceback ([Real Python](https://realpython.com/python-raise-exception/); [Python docs](https://docs.python.org/3/library/exceptions.html)). It is the safer default than `from None` because it does **not** hide an *installed-but-broken* dependency's real error — which, given tracefork injects into undocumented framework internals, is the exact confusing `AttributeError` you want surfaced. This is "best practice," not "strictly required" (`from None` is defensible for the pure missing-package case).
77
+
78
+ **Optional (not load-bearing):** a pandas-style `require_extra(module, extra)` DRY helper. Worthwhile only if adapters grow; for ~5 adapters the duplication is small. **Do NOT adopt `lazy_loader`;** PEP 562 `__getattr__` is optional polish, not needed here.
79
+
80
+ ---
81
+
82
+ ## 3. Concrete recommendation for tracefork
83
+
84
+ **Decisions, sequenced.**
85
+
86
+ ### For the next 0.2.1 release (do now — pyproject + docs + one runtime idiom)
87
+
88
+ **1. Keep per-integration extras.** No structural change; they are the only bracket-installable mechanism.
89
+
90
+ **2. Relax the speculative caps to floors; keep only the internal-coupling caps, each with an inline rationale + revisit note.** Example shape:
91
+
92
+ ```toml
93
+ [project.optional-dependencies]
94
+ # stable wire-format / self-instrumentation — floors only
95
+ providers = ["openai>=1.0", "google-genai>=0.3"]
96
+ bedrock = ["boto3>=1.34"]
97
+ mcp = ["mcp>=1.0"]
98
+ observability = ["structlog>=24.1", "opentelemetry-api>=1.27", "opentelemetry-sdk>=1.27"]
99
+
100
+ # LangChain 1.0 GA promises no breaks until 2.0 -> floor only
101
+ # (a <2 cap is useless against the real intra-1.x patch regressions)
102
+ frameworks = ["langchain-core>=1.4", "langchain-openai>=1.3", "langchain-anthropic>=1.4", "langgraph>=1.2"]
103
+
104
+ # CAPS: adapter injects into private/undocumented internals (Schreiner's blessed
105
+ # exception). Each cap is a hint, NOT a guard — CI-against-latest is the guard.
106
+ # TODO(caps): test newest major each release and widen.
107
+ openai-agents = ["openai-agents>=0.10,<1"] # 0.x pre-stable API
108
+ autogen = ["autogen-core>=0.7,<1", "autogen-ext>=0.7,<1"] # experimental rewrite
109
+ crewai = ["crewai>=1.0,<2"] # hooks LiteLLM private client_session
110
+ adk = ["google-adk>=2.3,<3"] # stable but fast-major + internal coupling
111
+
112
+ # curated convenience extra — self-referential, deliberately EXCLUDES the
113
+ # mutually-heavy framework stacks and dev
114
+ all = ["tracefork[providers,bedrock,mcp,observability]"]
115
+ ```
116
+
117
+ **Self-referencing extras are safe here.** Under hatchling the `all` self-reference is flattened into concrete `Requires-Dist` at build time. **Required post-edit step (not a formality):** run `uv sync --extra all` and `uv run pytest -q` to confirm resolution and that the offline suite still passes.
118
+
119
+ *Why exclude the framework stacks from `all`:* unioning five independently-capped, fast-moving frameworks makes `all` a single all-or-nothing failure target — one future cap collision `ResolutionImpossible`s the whole install though each extra installs fine alone. The curated `all` mirrors dask's internally-consistent `complete`, not pandas' god-`all`.
120
+
121
+ **3. Upgrade the runtime guard to chain the cause.** Sketch:
122
+
123
+ ```python
124
+ # tracefork/adapters/base.py
125
+ def require_extra(module: str, extra: str):
126
+ """Import `module` or raise an actionable, chained ImportError."""
127
+ try:
128
+ return importlib.import_module(module)
129
+ except ImportError as exc: # keep `from exc` — preserves an installed-but-broken dep's real error
130
+ raise ImportError(
131
+ f"tracefork's {extra!r} integration needs {module!r}. "
132
+ f"Install it with: pip install 'tracefork[{extra}]'"
133
+ ) from exc
134
+ ```
135
+
136
+ **4. Docs:** quote every bracketed install snippet in `README`/`CLAUDE.md` (`pip install 'tracefork[all]'`), and add a small **extras table** disambiguating the generic names.
137
+
138
+ **5. Register an `entry-points` group** (`tracefork.adapters`) resolved by `adapters/base.py`, so a future single-adapter extraction is non-breaking.
139
+
140
+ ### Later (deliberate, higher-churn changes)
141
+
142
+ **6. Migrate `dev` to `[dependency-groups]`** — breaking to the dev command surface; update `CLAUDE.md`/`README`/`scripts/e2e.sh` to `uv sync --group dev` in the same commit.
143
+
144
+ **7. Add runtime version/feature checks** to the internal-coupling adapters (the real guard the caps only hint at).
145
+
146
+ ### Explicitly decided against
147
+
148
+ - **No `tracefork-*` package split.** Split trigger = an adapter needing decoupled cadence, a co-maintainer, or a conflicting hard dependency — never count.
149
+ - **No god-`all` over the frameworks.**
150
+ - **No rename of extras for conformance** — already PEP 685/503 clean.
151
+
152
+ ---
153
+
154
+ ## 4. What NOT to do
155
+
156
+ - **Do NOT put user-facing integrations in `[dependency-groups]`.** Excluded from published metadata, no bracket-install interface.
157
+ - **Do NOT design around PEP 771 default-extras.** Still Draft, no shipped tooling.
158
+ - **Do NOT ship a single god-`all` over the five fast-moving frameworks.**
159
+ - **Do NOT keep preemptive `<2`/`<3` caps on stable-wire deps.** A library upper-cap can't be overridden downstream and is permanent once on PyPI.
160
+ - **Do NOT treat a single-major cap as protection against private-internal breakage.** Those ship in minor/patch releases inside the allowed range.
161
+ - **Do NOT let a caught `ImportError` swallow the original.** Re-raise with `from exc`.
162
+ - **Do NOT leave unquoted bracket install commands in docs.** zsh globs them.
163
+ - **Do NOT rename extras to add underscores or rely on old-pip normalization.**
164
+ - **Do NOT split adapters into separate packages "for cleanliness."**
165
+ - **Do NOT migrate `dev` off extras as a "quick additive edit."**
166
+ - **Do NOT reach for `lazy_loader` or PEP 562 `__getattr__`** — unnecessary for ~5 adapters.
167
+
168
+ ---
169
+
170
+ *Method: 13-agent research workflow (6 dimensions → adversarial verification → synthesis), all claims web-sourced against primary references. Generated 2026-07-03.*
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tracefork"
3
- version = "0.2.0"
3
+ version = "0.2.1"
4
4
  description = "Time-travel debugger for AI agents: bit-exact record/replay, fork any step, causal blame with confidence intervals."
5
5
  requires-python = ">=3.12"
6
6
  dependencies = [
@@ -67,20 +67,25 @@ mcp = [
67
67
  # Optional: LangChain / LangGraph, only needed to bind a *live* framework run to
68
68
  # tracefork's transport seam (record) or replay a graph from a tape. The adapter
69
69
  # guards every framework import, so `import tracefork` and the whole offline test
70
- # suite work with none of these installed — they are NOT hard dependencies. Pin
71
- # ranges are conservative (these packages churn fast); widen as versions are
72
- # validated against the adapter.
70
+ # suite work with none of these installed — they are NOT hard dependencies.
71
+ # Floors only (no `<2` cap): LangChain 1.0 GA commits to no breaking changes
72
+ # until 2.0, so a `<2` cap is speculative AND useless against the real failure
73
+ # mode — intra-1.x patch regressions (e.g. langgraph-prebuilt 1.0.2/1.0.5/1.0.9)
74
+ # ship *inside* the allowed range, so only CI-against-latest (+ a targeted `!=`
75
+ # if a bad release is ever confirmed) actually catches those, not a major cap.
73
76
  frameworks = [
74
- "langchain-core>=1.4,<2",
75
- "langchain-openai>=1.3,<2",
76
- "langchain-anthropic>=1.4,<2",
77
- "langgraph>=1.2,<2",
77
+ "langchain-core>=1.4",
78
+ "langchain-openai>=1.3",
79
+ "langchain-anthropic>=1.4",
80
+ "langgraph>=1.2",
78
81
  ]
79
82
  # Optional: the OpenAI Agents SDK, only needed to bind a *live* Agents SDK run's
80
83
  # underlying openai client to tracefork's transport seam (`adapters/openai_agents.py`),
81
84
  # or to install its tracing core as a real TracingProcessor. The adapter guards
82
85
  # every import, so `import tracefork` and the whole offline test suite work with
83
- # it NOT installed. Pin range is conservative (this package churns fast).
86
+ # it NOT installed. CAP: 0.x pre-stable API, and the adapter injects into
87
+ # private/undocumented internals — the cap is a hint, not a guard; CI-against-
88
+ # latest is the real guard. TODO(caps): test newest major each release and widen.
84
89
  "openai-agents" = [
85
90
  "openai-agents>=0.10,<1",
86
91
  ]
@@ -90,6 +95,9 @@ frameworks = [
90
95
  # handed), so `import tracefork` and the whole offline test suite work with
91
96
  # neither installed. litellm — the actual httpx chokepoint `bind()` targets —
92
97
  # is crewai's own transitive dependency, so it is not pinned separately here.
98
+ # CAP: the adapter hooks LiteLLM's private `client_session`/`aclient_session`
99
+ # internals — the cap is a hint, not a guard; CI-against-latest is the real
100
+ # guard. TODO(caps): test newest major each release and widen.
93
101
  crewai = [
94
102
  "crewai>=1.0,<2",
95
103
  ]
@@ -97,11 +105,25 @@ crewai = [
97
105
  # autogen-ext (the OpenAIChatCompletionClient model wrapper `bind()` targets),
98
106
  # only needed to exercise `adapters/autogen.py` against the real framework.
99
107
  # Both imports are guarded, so `import tracefork` and the whole offline test
100
- # suite work with neither installed.
108
+ # suite work with neither installed. CAP: genuinely experimental/pre-stable
109
+ # (ground-up async rewrite) — the cap is a hint, not a guard; CI-against-latest
110
+ # is the real guard. TODO(caps): test newest major each release and widen.
101
111
  autogen = [
102
112
  "autogen-core>=0.7,<1",
103
113
  "autogen-ext>=0.7,<1",
104
114
  ]
115
+ # Optional: google-adk (the Google Agent Development Kit), only needed to bind a
116
+ # *live* ADK LlmAgent/Runner's underlying google-genai client to tracefork's
117
+ # transport seam (`adapters/adk.py`), or to install its BasePlugin as a real
118
+ # ADK plugin. google-adk pulls in google-genai transitively (it already pins
119
+ # `google-genai>=2.8,<3`), so it is not pinned separately here. Both imports
120
+ # are guarded, so `import tracefork` and the whole offline test suite work
121
+ # with neither installed. CAP: stable (not pre-1.0) but fast major cadence +
122
+ # adapter-internal coupling — the cap is a hint, not a guard; CI-against-latest
123
+ # is the real guard. TODO(caps): test newest major each release and widen.
124
+ adk = [
125
+ "google-adk>=2.3,<3",
126
+ ]
105
127
  # Optional: structlog JSON logging + OTel self-instrumentation of
106
128
  # record/replay/fork/blame (see observability.py). Export/ingest of OTel
107
129
  # GenAI / OpenInference data (interop.py) needs NEITHER of these — it produces
@@ -111,6 +133,12 @@ observability = [
111
133
  "opentelemetry-api>=1.27",
112
134
  "opentelemetry-sdk>=1.27",
113
135
  ]
136
+ # Curated convenience extra: the stable-wire / self-instrumentation family in one
137
+ # shot. DELIBERATELY excludes the fast-moving framework stacks (frameworks,
138
+ # openai-agents, crewai, autogen, adk) and dev — unioning five independently-capped
139
+ # frameworks would make `all` a single all-or-nothing resolution target. Self-
140
+ # referential extra (hatchling flattens it into concrete Requires-Dist at build).
141
+ all = ["tracefork[providers,bedrock,mcp,observability]"]
114
142
 
115
143
  [project.scripts]
116
144
  tracefork = "tracefork.cli:app"
@@ -1,6 +1,8 @@
1
1
  """tracefork — time-travel debugger for AI agents."""
2
2
 
3
3
  from .adapters import (
4
+ ADK_IMPORT_HINT,
5
+ AdkAdapter,
4
6
  AutoGenAdapter,
5
7
  BaseFrameworkAdapter,
6
8
  BindResult,
@@ -11,10 +13,12 @@ from .adapters import (
11
13
  Step,
12
14
  StepDAG,
13
15
  TapeBackedCheckpointStore,
16
+ TraceforkAdkCore,
14
17
  TraceforkCallbackCore,
15
18
  TraceforkCrewEventCore,
16
19
  TraceforkInterventionCore,
17
20
  TraceforkTracingCore,
21
+ adk_available,
18
22
  autogen_available,
19
23
  bind_default_client,
20
24
  crewai_available,
@@ -24,11 +28,13 @@ from .adapters import (
24
28
  make_callback_handler,
25
29
  make_event_listener,
26
30
  make_intervention_handler,
31
+ make_plugin,
27
32
  make_tape_backed_checkpointer,
28
33
  make_tracing_processor,
29
34
  openai_agents_available,
30
35
  register_framework_adapter,
31
36
  registered_framework_adapters,
37
+ require_adk,
32
38
  require_autogen,
33
39
  require_crewai,
34
40
  require_langchain,
@@ -131,4 +137,10 @@ __all__ = [
131
137
  "autogen_available",
132
138
  "make_intervention_handler",
133
139
  "require_autogen",
140
+ "ADK_IMPORT_HINT",
141
+ "AdkAdapter",
142
+ "TraceforkAdkCore",
143
+ "adk_available",
144
+ "make_plugin",
145
+ "require_adk",
134
146
  ]