agentic-lab 0.2.0__tar.gz → 0.3.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 (171) hide show
  1. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/PKG-INFO +3 -1
  2. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/pyproject.toml +13 -1
  3. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/__init__.py +30 -2
  4. agentic_lab-0.3.0/src/agentlab/bundle.py +247 -0
  5. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/cli.py +224 -1
  6. agentic_lab-0.3.0/src/agentlab/core/autoenvelope.py +201 -0
  7. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/core/context.py +20 -0
  8. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/core/run.py +13 -0
  9. agentic_lab-0.3.0/src/agentlab/decorators.py +177 -0
  10. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/evals/cli.py +150 -0
  11. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/evals/suite.py +8 -0
  12. agentic_lab-0.3.0/src/agentlab/feedback.py +290 -0
  13. agentic_lab-0.3.0/src/agentlab/integrations/__init__.py +133 -0
  14. agentic_lab-0.3.0/src/agentlab/integrations/langchain/__init__.py +99 -0
  15. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/integrations/langchain/_callbacks.py +23 -1
  16. agentic_lab-0.3.0/src/agentlab/integrations/openai_agents.py +162 -0
  17. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/io/http_capture.py +35 -7
  18. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/llm/_autoemit.py +48 -19
  19. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/promote.py +204 -34
  20. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/recorder.py +30 -0
  21. agentic_lab-0.3.0/src/agentlab/replay/whatif.py +366 -0
  22. agentic_lab-0.3.0/src/agentlab/session.py +217 -0
  23. agentic_lab-0.3.0/src/agentlab/share.py +358 -0
  24. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/storage/jsonl_store.py +11 -7
  25. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/storage/proto_store.py +10 -7
  26. agentic_lab-0.3.0/src/agentlab/ui/_assets/dist/assets/index-DrMO8lw4.css +1 -0
  27. agentic_lab-0.3.0/src/agentlab/ui/_assets/dist/assets/index-DvaPDz-C.js +215 -0
  28. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/ui/_assets/dist/index.html +2 -2
  29. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/ui/_dto.py +3 -0
  30. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/ui/_mapping.py +15 -2
  31. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/ui/_server/app.py +46 -2
  32. agentic_lab-0.3.0/src/agentlab/ui/_server/diff.py +129 -0
  33. agentic_lab-0.3.0/src/agentlab/ui/_server/feedback.py +127 -0
  34. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/ui/_server/replay_runner.py +152 -8
  35. agentic_lab-0.3.0/src/agentlab/ui/_server/whatif.py +142 -0
  36. agentic_lab-0.2.0/src/agentlab/integrations/__init__.py +0 -6
  37. agentic_lab-0.2.0/src/agentlab/integrations/langchain/__init__.py +0 -15
  38. agentic_lab-0.2.0/src/agentlab/ui/_assets/dist/assets/index-Cwxk3vuT.css +0 -1
  39. agentic_lab-0.2.0/src/agentlab/ui/_assets/dist/assets/index-w0GuiiTW.js +0 -211
  40. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/README.md +0 -0
  41. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/_defaults.toml +0 -0
  42. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/_proto/__init__.py +0 -0
  43. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/_proto/trace_pb2.py +0 -0
  44. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/_proto/trace_pb2.pyi +0 -0
  45. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/bridges/__init__.py +0 -0
  46. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/bridges/otel_genai.py +0 -0
  47. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/config.py +0 -0
  48. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/core/__init__.py +0 -0
  49. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/core/_clock.py +0 -0
  50. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/core/_fmt.py +0 -0
  51. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/core/_replay_seam.py +0 -0
  52. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/core/agent.py +0 -0
  53. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/core/attributes.py +0 -0
  54. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/core/budget.py +0 -0
  55. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/core/continuation.py +0 -0
  56. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/core/cost.py +0 -0
  57. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/core/determinism.py +0 -0
  58. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/core/env.py +0 -0
  59. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/core/eval.py +0 -0
  60. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/core/guardrail.py +0 -0
  61. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/core/handles.py +0 -0
  62. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/core/handoff.py +0 -0
  63. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/core/ids.py +0 -0
  64. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/core/link.py +0 -0
  65. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/core/llm.py +0 -0
  66. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/core/memory.py +0 -0
  67. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/core/message.py +0 -0
  68. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/core/op.py +0 -0
  69. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/core/op_names.py +0 -0
  70. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/core/retrieval.py +0 -0
  71. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/core/schema.py +0 -0
  72. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/core/span.py +0 -0
  73. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/core/span_event.py +0 -0
  74. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/core/status.py +0 -0
  75. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/core/step.py +0 -0
  76. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/core/tool.py +0 -0
  77. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/core/types.py +0 -0
  78. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/errors.py +0 -0
  79. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/evals/__init__.py +0 -0
  80. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/evals/_readable_trace.py +0 -0
  81. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/evals/_score_parser.py +0 -0
  82. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/evals/errors.py +0 -0
  83. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/evals/evaluator.py +0 -0
  84. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/evals/fakes.py +0 -0
  85. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/evals/judge_prompts/__init__.py +0 -0
  86. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/evals/judge_prompts/citation_grounding.md +0 -0
  87. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/evals/judge_prompts/faithfulness.md +0 -0
  88. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/evals/judges.py +0 -0
  89. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/evals/probes/__init__.py +0 -0
  90. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/evals/probes/_base.py +0 -0
  91. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/evals/probes/contains_substring.py +0 -0
  92. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/evals/probes/conversation_length.py +0 -0
  93. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/evals/probes/latency.py +0 -0
  94. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/evals/probes/token_budget.py +0 -0
  95. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/evals/probes/tool_usage.py +0 -0
  96. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/evals/registry.py +0 -0
  97. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/evals/rubrics.py +0 -0
  98. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/evals/run.py +0 -0
  99. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/evals/run_executor.py +0 -0
  100. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/evals/runner.py +0 -0
  101. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/evals/types.py +0 -0
  102. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/examples/README.md +0 -0
  103. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/examples/__init__.py +0 -0
  104. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/examples/_shared/__init__.py +0 -0
  105. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/examples/_shared/datasets.py +0 -0
  106. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/examples/_shared/llm.py +0 -0
  107. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/examples/_shared/wikipedia.py +0 -0
  108. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/examples/autonomous/__init__.py +0 -0
  109. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/examples/autonomous/triage_agent.py +0 -0
  110. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/examples/hybrid/__init__.py +0 -0
  111. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/examples/hybrid/incident_responder.py +0 -0
  112. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/examples/quickstart.py +0 -0
  113. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/examples/workflows/__init__.py +0 -0
  114. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/examples/workflows/research_assistant.py +0 -0
  115. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/examples/workflows/scholar_swarm.py +0 -0
  116. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/integrations/langchain/_helpers.py +0 -0
  117. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/io/__init__.py +0 -0
  118. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/io/canonical.py +0 -0
  119. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/io/format.py +0 -0
  120. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/io/http_format.py +0 -0
  121. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/io/otel_attributes.py +0 -0
  122. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/io/proto/__init__.py +0 -0
  123. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/io/proto/codec.py +0 -0
  124. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/io/proto/framing.py +0 -0
  125. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/io/proto/reader.py +0 -0
  126. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/io/proto/writer.py +0 -0
  127. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/io/reader.py +0 -0
  128. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/io/redaction.py +0 -0
  129. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/io/writer.py +0 -0
  130. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/llm/__init__.py +0 -0
  131. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/llm/base.py +0 -0
  132. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/llm/errors.py +0 -0
  133. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/llm/factory.py +0 -0
  134. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/llm/matchers/__init__.py +0 -0
  135. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/llm/matchers/_common.py +0 -0
  136. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/llm/matchers/anthropic.py +0 -0
  137. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/llm/matchers/bedrock.py +0 -0
  138. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/llm/matchers/google.py +0 -0
  139. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/llm/matchers/openai.py +0 -0
  140. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/llm/pricing.py +0 -0
  141. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/llm/providers/__init__.py +0 -0
  142. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/llm/providers/openrouter.py +0 -0
  143. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/llm/types.py +0 -0
  144. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/py.typed +0 -0
  145. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/pytest.py +0 -0
  146. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/replay/__init__.py +0 -0
  147. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/replay/api.py +0 -0
  148. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/replay/cache.py +0 -0
  149. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/replay/context.py +0 -0
  150. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/replay/diff.py +0 -0
  151. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/replay/drift.py +0 -0
  152. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/replay/session.py +0 -0
  153. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/replay/unfreeze.py +0 -0
  154. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/storage/__init__.py +0 -0
  155. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/storage/base.py +0 -0
  156. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/storage/factory.py +0 -0
  157. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/storage/names.py +0 -0
  158. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/ui/README.md +0 -0
  159. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/ui/__init__.py +0 -0
  160. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/ui/_agents.py +0 -0
  161. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/ui/_assets/__init__.py +0 -0
  162. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/ui/_assets/dist/favicon.svg +0 -0
  163. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/ui/_errors.py +0 -0
  164. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/ui/_runtime.py +0 -0
  165. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/ui/_server/__init__.py +0 -0
  166. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/ui/_server/agents.py +0 -0
  167. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/ui/_server/agents_overview.py +0 -0
  168. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/ui/_server/dashboard.py +0 -0
  169. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/ui/_server/evals.py +0 -0
  170. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/ui/_server/validation.py +0 -0
  171. {agentic_lab-0.2.0 → agentic_lab-0.3.0}/src/agentlab/ui/_server/watcher.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: agentic-lab
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Universal record-and-replay for LLM agents.
5
5
  Keywords: llm,agents,replay,tracing,evals
6
6
  Author: Ambuj Agrawal, Garima Luthra
@@ -35,6 +35,7 @@ Requires-Dist: playwright>=1.59,<2.0 ; extra == 'dev'
35
35
  Requires-Dist: langchain-core>=0.3,<1.0 ; extra == 'langchain'
36
36
  Requires-Dist: langchain-openai>=0.3.35 ; extra == 'langchain'
37
37
  Requires-Dist: langgraph>=1.0.1 ; extra == 'langchain'
38
+ Requires-Dist: openai-agents>=0.0.1 ; extra == 'openai-agents'
38
39
  Requires-Dist: starlette>=0.40,<1.0 ; extra == 'ui'
39
40
  Requires-Dist: uvicorn>=0.30,<1.0 ; extra == 'ui'
40
41
  Requires-Dist: anyio>=4.4,<5.0 ; extra == 'ui'
@@ -50,6 +51,7 @@ Project-URL: Examples, https://github.com/ambuj-krishna-agrawal/agentlab/tree/ma
50
51
  Project-URL: Changelog, https://github.com/ambuj-krishna-agrawal/agentlab/blob/main/CHANGELOG.md
51
52
  Provides-Extra: dev
52
53
  Provides-Extra: langchain
54
+ Provides-Extra: openai-agents
53
55
  Provides-Extra: ui
54
56
  Description-Content-Type: text/markdown
55
57
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agentic-lab"
3
- version = "0.2.0"
3
+ version = "0.3.0"
4
4
  description = "Universal record-and-replay for LLM agents."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -43,6 +43,9 @@ langchain = [
43
43
  "langchain-openai>=0.3.35",
44
44
  "langgraph>=1.0.1",
45
45
  ]
46
+ openai-agents = [
47
+ "openai-agents>=0.0.1",
48
+ ]
46
49
  dev = [
47
50
  "pytest>=8.2,<9.0",
48
51
  "pytest-asyncio>=0.23,<1.0",
@@ -176,6 +179,9 @@ disable_error_code = [
176
179
  "no-any-return",
177
180
  "arg-type",
178
181
  "unused-ignore",
182
+ "untyped-decorator",
183
+ "union-attr",
184
+ "operator",
179
185
  ]
180
186
 
181
187
  [[tool.mypy.overrides]]
@@ -192,6 +198,12 @@ module = ["agentlab.examples.*"]
192
198
  ignore_errors = true
193
199
  follow_imports = "skip"
194
200
 
201
+ [[tool.mypy.overrides]]
202
+ # Optional integration deps that are not test/runtime dependencies of the
203
+ # core package (imported lazily inside ``install()``).
204
+ module = ["agents.*", "langchain_core.*"]
205
+ ignore_missing_imports = true
206
+
195
207
  [tool.pytest.ini_options]
196
208
  minversion = "8.0"
197
209
  addopts = [
@@ -38,6 +38,10 @@ without stability guarantees.
38
38
  from importlib.metadata import PackageNotFoundError as _PackageNotFoundError
39
39
  from importlib.metadata import version as _version
40
40
 
41
+ # Subpackage exposed for ``al.integrations.install(...)`` and lazy
42
+ # ``al.integrations.langchain``. The package ``__init__`` is dependency
43
+ # free; optional adapters import their framework only inside ``install()``.
44
+ from agentlab import integrations
41
45
  from agentlab.bridges.otel_genai import ExportTarget, export_trace
42
46
  from agentlab.core import (
43
47
  AgentHandle,
@@ -97,7 +101,6 @@ from agentlab.core import (
97
101
  ToolHandle,
98
102
  ToolKind,
99
103
  ToolSpan,
100
- agent,
101
104
  continuation,
102
105
  detached,
103
106
  eval_,
@@ -112,10 +115,17 @@ from agentlab.core import (
112
115
  retriever,
113
116
  start_run,
114
117
  start_span,
115
- step,
116
118
  tool,
117
119
  )
118
120
  from agentlab.core import llm as _llm_emitter
121
+
122
+ # Dual-use decorators. These shadow the bare ``agent`` / ``step``
123
+ # context-managers imported from ``agentlab.core`` above with wrappers
124
+ # that work as *both* context managers and (sync/async-aware)
125
+ # decorators, and add the new ``run`` decorator. ``with al.agent(...)``
126
+ # behaves identically; ``@al.agent(...)`` / ``@al.step(...)`` /
127
+ # ``@al.run(...)`` are the new spellings.
128
+ from agentlab.decorators import agent, run, step
119
129
  from agentlab.evals import (
120
130
  AgentRunner,
121
131
  BaseEvaluator,
@@ -175,6 +185,13 @@ from agentlab.replay import (
175
185
  replay,
176
186
  replay_run,
177
187
  )
188
+ from agentlab.session import (
189
+ _maybe_autostart_from_env,
190
+ active_recording,
191
+ init,
192
+ is_active,
193
+ shutdown,
194
+ )
178
195
  from agentlab.ui import create_app, serve
179
196
 
180
197
  # ``agentlab.recorder`` transitively imports ``agentlab.llm._autoemit``;
@@ -193,6 +210,11 @@ try:
193
210
  except _PackageNotFoundError: # pragma: no cover — only when running uninstalled
194
211
  __version__ = "0.0.0+unknown"
195
212
 
213
+ # ``AGENTLAB=1 python my_agent.py`` records everything with zero code
214
+ # change. This must run *after* the whole public surface is imported so
215
+ # the ambient ``record()`` it opens sees a fully-initialised package.
216
+ _maybe_autostart_from_env()
217
+
196
218
  __all__ = [
197
219
  "CURRENT_SCHEMA_VERSION",
198
220
  "AgentHandle",
@@ -294,6 +316,7 @@ __all__ = [
294
316
  "TraceHeader",
295
317
  "UnfreezeRef",
296
318
  "__version__",
319
+ "active_recording",
297
320
  "agent",
298
321
  "bundled_rubric",
299
322
  "continuation",
@@ -306,6 +329,9 @@ __all__ = [
306
329
  "guardrail",
307
330
  "handoff",
308
331
  "in_recording",
332
+ "init",
333
+ "integrations",
334
+ "is_active",
309
335
  "list_bundled_rubrics",
310
336
  "llm",
311
337
  "memory",
@@ -320,7 +346,9 @@ __all__ = [
320
346
  "replay",
321
347
  "replay_run",
322
348
  "retriever",
349
+ "run",
323
350
  "serve",
351
+ "shutdown",
324
352
  "start_run",
325
353
  "start_span",
326
354
  "step",
@@ -0,0 +1,247 @@
1
+ """Portable single-file trace bundles (``*.agentlab``).
2
+
3
+ A ``.agentlab`` file is a plain ZIP archive of one trace directory plus a
4
+ small ``bundle.json`` manifest. Because the archive contains
5
+ ``http.jsonl`` (the raw on-the-wire exchanges), the recipient can not
6
+ only *view* the trace but **replay it deterministically** — no API keys,
7
+ no network. This is the lightweight, on-brand version of "team
8
+ collaboration": send a colleague the exact failing run.
9
+
10
+ ::
11
+
12
+ agentlab bundle brave-fog ./bug.agentlab # author
13
+ agentlab open ./bug.agentlab # teammate → imports + prints key
14
+
15
+ Layout inside the archive::
16
+
17
+ bundle.json — manifest (format version, ulid, name, ...)
18
+ trace/header.json
19
+ trace/spans.jsonl
20
+ trace/footer.json — absent ⇒ the run crashed
21
+ trace/http.jsonl — present iff HTTP was captured
22
+ trace/... — every other file in the source dir, 1:1
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import json
28
+ import zipfile
29
+ from dataclasses import asdict, dataclass
30
+ from datetime import UTC, datetime
31
+ from pathlib import Path
32
+ from typing import TYPE_CHECKING
33
+
34
+ from agentlab.errors import TraceCorruptionError, TraceKeyNotFoundError
35
+ from agentlab.io.format import HEADER_FILENAME
36
+
37
+ if TYPE_CHECKING:
38
+ from agentlab.storage.base import TraceStore
39
+
40
+ __all__ = [
41
+ "BUNDLE_FORMAT_VERSION",
42
+ "BUNDLE_SUFFIX",
43
+ "BundleManifest",
44
+ "bundle_trace",
45
+ "open_bundle",
46
+ ]
47
+
48
+ BUNDLE_SUFFIX = ".agentlab"
49
+ BUNDLE_MANIFEST = "bundle.json"
50
+ BUNDLE_FORMAT_VERSION = 1
51
+ _TRACE_PREFIX = "trace/"
52
+
53
+
54
+ @dataclass(frozen=True, slots=True)
55
+ class BundleManifest:
56
+ """Self-describing header written into every bundle."""
57
+
58
+ format_version: int
59
+ trace_ulid: str
60
+ trace_name: str
61
+ dir_name: str
62
+ created_at: str
63
+ file_count: int
64
+
65
+
66
+ def bundle_trace(
67
+ source_dir: Path,
68
+ out: Path,
69
+ *,
70
+ force: bool = False,
71
+ ) -> Path:
72
+ """Archive the trace directory ``source_dir`` into a ``.agentlab`` file.
73
+
74
+ Returns the written path. Raises :class:`TraceKeyNotFoundError` if
75
+ ``source_dir`` is not a trace directory, and refuses to clobber an
76
+ existing ``out`` unless ``force`` is set.
77
+ """
78
+ source_dir = source_dir.expanduser().resolve()
79
+ header_path = source_dir / HEADER_FILENAME
80
+ if not header_path.is_file():
81
+ raise TraceKeyNotFoundError(
82
+ f"{source_dir} is not a trace directory (no {HEADER_FILENAME}); cannot bundle.",
83
+ trace_key=str(source_dir),
84
+ )
85
+
86
+ out = out.expanduser()
87
+ if out.suffix != BUNDLE_SUFFIX:
88
+ out = out.with_suffix(BUNDLE_SUFFIX)
89
+ if out.exists() and not force:
90
+ raise FileExistsError(
91
+ f"bundle target {out} already exists; pass --force to overwrite.",
92
+ )
93
+ out.parent.mkdir(parents=True, exist_ok=True)
94
+
95
+ files = sorted(p for p in source_dir.rglob("*") if p.is_file())
96
+ ulid, name = _read_identity(header_path)
97
+
98
+ manifest = BundleManifest(
99
+ format_version=BUNDLE_FORMAT_VERSION,
100
+ trace_ulid=ulid,
101
+ trace_name=name,
102
+ dir_name=source_dir.name,
103
+ created_at=datetime.now(UTC).isoformat(),
104
+ file_count=len(files),
105
+ )
106
+
107
+ # Write atomically: build a temp file then rename so a crash never
108
+ # leaves a half-written ``.agentlab`` that looks valid.
109
+ tmp = out.with_name(out.name + ".tmp")
110
+ try:
111
+ with zipfile.ZipFile(tmp, "w", compression=zipfile.ZIP_DEFLATED) as zf:
112
+ zf.writestr(BUNDLE_MANIFEST, json.dumps(asdict(manifest), indent=2))
113
+ for file_path in files:
114
+ arcname = _TRACE_PREFIX + file_path.relative_to(source_dir).as_posix()
115
+ zf.write(file_path, arcname)
116
+ tmp.replace(out)
117
+ finally:
118
+ tmp.unlink(missing_ok=True)
119
+ return out
120
+
121
+
122
+ def open_bundle(
123
+ bundle_path: Path,
124
+ store: TraceStore,
125
+ *,
126
+ force: bool = False,
127
+ ) -> Path:
128
+ """Extract a ``.agentlab`` bundle into ``store``'s root and return the dir.
129
+
130
+ The trace is placed under its original directory name so it resolves
131
+ by ULID / name exactly as it did for the author. Refuses to
132
+ overwrite an existing trace directory unless ``force`` is set.
133
+ """
134
+ bundle_path = bundle_path.expanduser().resolve()
135
+ if not bundle_path.is_file():
136
+ raise TraceKeyNotFoundError(
137
+ f"bundle {bundle_path} does not exist.",
138
+ trace_key=str(bundle_path),
139
+ )
140
+ root = _store_root(store)
141
+
142
+ with zipfile.ZipFile(bundle_path) as zf:
143
+ manifest = _read_manifest(zf, bundle_path)
144
+ dir_name = manifest.dir_name or manifest.trace_ulid
145
+ if not dir_name:
146
+ raise TraceCorruptionError(
147
+ f"bundle {bundle_path} has no usable trace directory name.",
148
+ )
149
+ target = root / dir_name
150
+ if target.exists():
151
+ if not force:
152
+ raise FileExistsError(
153
+ f"a trace directory already exists at {target}; pass --force to overwrite it.",
154
+ )
155
+ _rmtree(target)
156
+
157
+ members = [n for n in zf.namelist() if n.startswith(_TRACE_PREFIX)]
158
+ if not members:
159
+ raise TraceCorruptionError(
160
+ f"bundle {bundle_path} contains no trace/ payload.",
161
+ )
162
+ root.mkdir(parents=True, exist_ok=True)
163
+ target.mkdir(parents=True, exist_ok=True)
164
+ for member in members:
165
+ _safe_extract_member(zf, member, target)
166
+
167
+ return target
168
+
169
+
170
+ # ---------------------------------------------------------------------------
171
+ # Helpers
172
+ # ---------------------------------------------------------------------------
173
+
174
+
175
+ def _read_identity(header_path: Path) -> tuple[str, str]:
176
+ """Return ``(trace_ulid, trace_name)`` from a JSON header (best effort)."""
177
+ try:
178
+ data = json.loads(header_path.read_text(encoding="utf-8"))
179
+ except (OSError, json.JSONDecodeError):
180
+ return "", ""
181
+ ulid = str(data.get("trace_ulid", "") or "")
182
+ name = str(data.get("name", "") or "")
183
+ return ulid, name
184
+
185
+
186
+ def _read_manifest(zf: zipfile.ZipFile, bundle_path: Path) -> BundleManifest:
187
+ try:
188
+ raw = zf.read(BUNDLE_MANIFEST)
189
+ except KeyError as exc:
190
+ raise TraceCorruptionError(
191
+ f"bundle {bundle_path} is missing its {BUNDLE_MANIFEST}; "
192
+ f"it may not be an AgentLab bundle.",
193
+ ) from exc
194
+ try:
195
+ data = json.loads(raw)
196
+ except json.JSONDecodeError as exc:
197
+ raise TraceCorruptionError(
198
+ f"bundle {bundle_path} has a corrupt {BUNDLE_MANIFEST}.",
199
+ ) from exc
200
+ return BundleManifest(
201
+ format_version=int(data.get("format_version", 0)),
202
+ trace_ulid=str(data.get("trace_ulid", "")),
203
+ trace_name=str(data.get("trace_name", "")),
204
+ dir_name=str(data.get("dir_name", "")),
205
+ created_at=str(data.get("created_at", "")),
206
+ file_count=int(data.get("file_count", 0)),
207
+ )
208
+
209
+
210
+ def _safe_extract_member(zf: zipfile.ZipFile, member: str, target: Path) -> None:
211
+ """Extract one ``trace/<rel>`` member into ``target`` (zip-slip safe)."""
212
+ rel = member[len(_TRACE_PREFIX) :]
213
+ if not rel or rel.endswith("/"):
214
+ return
215
+ dest = (target / rel).resolve()
216
+ # Defend against path traversal in a malicious bundle.
217
+ if not _is_within(dest, target.resolve()):
218
+ raise TraceCorruptionError(
219
+ f"bundle member {member!r} escapes the target directory; refusing.",
220
+ )
221
+ dest.parent.mkdir(parents=True, exist_ok=True)
222
+ with zf.open(member) as src, dest.open("wb") as out_fp:
223
+ out_fp.write(src.read())
224
+
225
+
226
+ def _is_within(path: Path, root: Path) -> bool:
227
+ try:
228
+ path.relative_to(root)
229
+ except ValueError:
230
+ return False
231
+ return True
232
+
233
+
234
+ def _store_root(store: TraceStore) -> Path:
235
+ root = getattr(store, "root", None)
236
+ if root is None:
237
+ raise TraceCorruptionError(
238
+ "the configured trace store has no local root; "
239
+ "`agentlab open` only supports local stores.",
240
+ )
241
+ return Path(root)
242
+
243
+
244
+ def _rmtree(path: Path) -> None:
245
+ import shutil # noqa: PLC0415
246
+
247
+ shutil.rmtree(path)
@@ -89,6 +89,90 @@ _log = logging.getLogger("agentlab.cli")
89
89
  # ---------------------------------------------------------------------------
90
90
 
91
91
 
92
+ def _add_share_subparsers(
93
+ sub: argparse._SubParsersAction[argparse.ArgumentParser],
94
+ ) -> None:
95
+ """Wire the portable-bundle ``bundle`` / ``open`` / ``push`` / ``pull`` subcommands."""
96
+ p = sub.add_parser(
97
+ "bundle",
98
+ help="Pack a trace into a portable single-file .agentlab bundle (replayable).",
99
+ )
100
+ p.add_argument("key")
101
+ p.add_argument("out", type=Path, help="Output path (``.agentlab`` is appended if missing).")
102
+ p.add_argument("--force", action="store_true", help="Overwrite an existing bundle.")
103
+
104
+ p = sub.add_parser(
105
+ "open",
106
+ help="Import a .agentlab bundle into the store and print its key.",
107
+ )
108
+ p.add_argument("bundle", type=Path, help="Path to a .agentlab bundle.")
109
+ p.add_argument(
110
+ "--force",
111
+ action="store_true",
112
+ help="Overwrite an existing trace directory with the same name.",
113
+ )
114
+
115
+ p = sub.add_parser(
116
+ "push",
117
+ help="Pack a trace into a bundle and upload it to a remote URL (http(s):// or file://).",
118
+ )
119
+ p.add_argument("key")
120
+ p.add_argument(
121
+ "url",
122
+ help=(
123
+ "Destination URL. For HTTPS use a presigned S3 / GCS / Azure URL "
124
+ "(or any server that accepts PUT)."
125
+ ),
126
+ )
127
+ p.add_argument(
128
+ "--auth",
129
+ default=None,
130
+ help=(
131
+ "Bearer token sent as ``Authorization: Bearer <auth>``. "
132
+ "Falls back to $AGENTLAB_BUNDLE_AUTH when omitted."
133
+ ),
134
+ )
135
+ p.add_argument(
136
+ "--force",
137
+ action="store_true",
138
+ help="For file:// destinations, overwrite an existing file.",
139
+ )
140
+ p.add_argument(
141
+ "--max-bytes",
142
+ type=int,
143
+ default=None,
144
+ help="Refuse to upload bundles larger than this (default: 1 GiB).",
145
+ )
146
+
147
+ p = sub.add_parser(
148
+ "pull",
149
+ help="Download a .agentlab bundle from a URL and import it into the store.",
150
+ )
151
+ p.add_argument(
152
+ "url",
153
+ help="Source URL (http(s):// or file://).",
154
+ )
155
+ p.add_argument(
156
+ "--auth",
157
+ default=None,
158
+ help=(
159
+ "Bearer token sent as ``Authorization: Bearer <auth>``. "
160
+ "Falls back to $AGENTLAB_BUNDLE_AUTH when omitted."
161
+ ),
162
+ )
163
+ p.add_argument(
164
+ "--force",
165
+ action="store_true",
166
+ help="Overwrite an existing trace directory with the same name.",
167
+ )
168
+ p.add_argument(
169
+ "--max-bytes",
170
+ type=int,
171
+ default=None,
172
+ help="Refuse to download bundles larger than this (default: 1 GiB).",
173
+ )
174
+
175
+
92
176
  def build_parser() -> argparse.ArgumentParser:
93
177
  """Build the top-level argparse parser (extracted for tests)."""
94
178
  from agentlab import __version__ # noqa: PLC0415 — avoid circular import on cold start
@@ -176,10 +260,19 @@ def build_parser() -> argparse.ArgumentParser:
176
260
  p.add_argument("--port", type=int, default=7861)
177
261
  p.add_argument("--open-browser", action="store_true")
178
262
 
263
+ _add_share_subparsers(sub)
264
+
179
265
  p = sub.add_parser("promote", help="Generate a pytest replay-test scaffold.")
180
266
  p.add_argument("key")
181
267
  p.add_argument("--out", type=Path, required=True)
182
- p.add_argument("--agent", required=True, help="module.path:callable_name")
268
+ p.add_argument(
269
+ "--agent",
270
+ default=None,
271
+ help=(
272
+ "module.path:callable_name. Optional when the run was recorded "
273
+ "via @al.run (the entrypoint is read from the trace)."
274
+ ),
275
+ )
183
276
  p.add_argument("--name", default=None)
184
277
  p.add_argument("--force", action="store_true")
185
278
 
@@ -277,6 +370,10 @@ def _dispatch(args: argparse.Namespace) -> int:
277
370
  "gc": _cmd_gc,
278
371
  "serve": _cmd_serve,
279
372
  "promote": _cmd_promote,
373
+ "bundle": _cmd_bundle,
374
+ "open": _cmd_open,
375
+ "push": _cmd_push,
376
+ "pull": _cmd_pull,
280
377
  }
281
378
  if args.command == "eval":
282
379
  from agentlab.evals.cli import dispatch as _eval_dispatch # noqa: PLC0415
@@ -497,6 +594,132 @@ def _cmd_export(args: argparse.Namespace) -> int:
497
594
  )
498
595
 
499
596
 
597
+ # ---------------------------------------------------------------------------
598
+ # bundle / open
599
+ # ---------------------------------------------------------------------------
600
+
601
+
602
+ def _cmd_bundle(args: argparse.Namespace) -> int:
603
+ from agentlab.bundle import bundle_trace # noqa: PLC0415
604
+
605
+ store = _resolve_store(args)
606
+ handle = store.resolve(args.key)
607
+ if handle.backend_locator is None:
608
+ raise TraceKeyNotFoundError(
609
+ "trace has no on-disk locator; cannot bundle.",
610
+ trace_key=args.key,
611
+ )
612
+ try:
613
+ out = bundle_trace(Path(handle.backend_locator), args.out, force=args.force)
614
+ except FileExistsError as exc:
615
+ sys.stderr.write(f"agentlab bundle: {exc}\n")
616
+ return EXIT_USER_ERROR
617
+ if args.json_output:
618
+ print(json.dumps({"out": str(out), "key": args.key}))
619
+ elif not args.quiet:
620
+ print(f"wrote bundle {out}")
621
+ return EXIT_OK
622
+
623
+
624
+ def _cmd_open(args: argparse.Namespace) -> int:
625
+ from agentlab.bundle import open_bundle # noqa: PLC0415
626
+
627
+ store = _resolve_store(args)
628
+ try:
629
+ target = open_bundle(args.bundle, store, force=args.force)
630
+ except FileExistsError as exc:
631
+ sys.stderr.write(f"agentlab open: {exc}\n")
632
+ return EXIT_USER_ERROR
633
+ handle = store.resolve(str(target))
634
+ key = handle.trace_ulid or handle.name or target.name
635
+ if args.json_output:
636
+ print(json.dumps({"imported": str(target), "key": key}))
637
+ elif not args.quiet:
638
+ print(f"imported trace {key}")
639
+ print(f" at: {target}")
640
+ print(f"\nView: agentlab show {key}")
641
+ print(f"Replay: agentlab replay {key}")
642
+ return EXIT_OK
643
+
644
+
645
+ def _cmd_push(args: argparse.Namespace) -> int:
646
+ from agentlab.share import BundleTransportError, push_bundle # noqa: PLC0415
647
+
648
+ store = _resolve_store(args)
649
+ handle = store.resolve(args.key)
650
+ if handle.backend_locator is None:
651
+ raise TraceKeyNotFoundError(
652
+ "trace has no on-disk locator; cannot push.",
653
+ trace_key=args.key,
654
+ )
655
+ push_kwargs: dict[str, Any] = {
656
+ "auth": args.auth,
657
+ "force": args.force,
658
+ }
659
+ if args.max_bytes is not None:
660
+ push_kwargs["max_bytes"] = args.max_bytes
661
+ try:
662
+ result = push_bundle(
663
+ Path(handle.backend_locator),
664
+ args.url,
665
+ **push_kwargs,
666
+ )
667
+ except BundleTransportError as exc:
668
+ sys.stderr.write(f"agentlab push: {exc.message}\n")
669
+ return EXIT_USER_ERROR
670
+ if args.json_output:
671
+ print(
672
+ json.dumps(
673
+ {
674
+ "url": result.url,
675
+ "bytes": result.bytes_uploaded,
676
+ "key": args.key,
677
+ },
678
+ ),
679
+ )
680
+ elif not args.quiet:
681
+ print(f"pushed {result.bytes_uploaded} bytes → {result.url}")
682
+ print(f"\nReceiver: agentlab pull {result.url}")
683
+ return EXIT_OK
684
+
685
+
686
+ def _cmd_pull(args: argparse.Namespace) -> int:
687
+ from agentlab.share import BundleTransportError, pull_bundle # noqa: PLC0415
688
+
689
+ store = _resolve_store(args)
690
+ pull_kwargs: dict[str, Any] = {
691
+ "auth": args.auth,
692
+ "force": args.force,
693
+ }
694
+ if args.max_bytes is not None:
695
+ pull_kwargs["max_bytes"] = args.max_bytes
696
+ try:
697
+ result = pull_bundle(args.url, store, **pull_kwargs)
698
+ except (BundleTransportError, FileExistsError) as exc:
699
+ msg = exc.message if isinstance(exc, BundleTransportError) else str(exc)
700
+ sys.stderr.write(f"agentlab pull: {msg}\n")
701
+ return EXIT_USER_ERROR
702
+ handle = store.resolve(str(result.trace_dir))
703
+ key = handle.trace_ulid or handle.name or result.trace_dir.name
704
+ if args.json_output:
705
+ print(
706
+ json.dumps(
707
+ {
708
+ "url": result.url,
709
+ "bytes": result.bytes_downloaded,
710
+ "imported": str(result.trace_dir),
711
+ "key": key,
712
+ },
713
+ ),
714
+ )
715
+ elif not args.quiet:
716
+ print(f"pulled {result.bytes_downloaded} bytes from {result.url}")
717
+ print(f"imported trace {key}")
718
+ print(f"\nView: agentlab show {key}")
719
+ print(f"Replay: agentlab replay {key}")
720
+ return EXIT_OK
721
+
722
+
500
723
  # ---------------------------------------------------------------------------
501
724
  # gc
502
725
  # ---------------------------------------------------------------------------