selectools 0.27.1__tar.gz → 0.28.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 (321) hide show
  1. {selectools-0.27.1/src/selectools.egg-info → selectools-0.28.0}/PKG-INFO +30 -7
  2. {selectools-0.27.1 → selectools-0.28.0}/README.md +24 -5
  3. {selectools-0.27.1 → selectools-0.28.0}/pyproject.toml +7 -2
  4. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/__init__.py +1 -1
  5. selectools-0.28.0/src/selectools/_ssrf.py +88 -0
  6. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/agent/_provider_caller.py +43 -21
  7. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/agent/_tool_executor.py +3 -2
  8. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/agent/config.py +27 -11
  9. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/agent/core.py +46 -21
  10. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/embeddings/anthropic.py +12 -1
  11. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/embeddings/cohere.py +12 -1
  12. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/embeddings/openai.py +8 -1
  13. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/evals/evaluators.py +1 -1
  14. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/mcp/multi.py +7 -1
  15. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/orchestration/graph.py +2 -4
  16. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/orchestration/supervisor.py +3 -1
  17. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/providers/anthropic_provider.py +4 -4
  18. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/rag/loaders.py +1 -54
  19. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/rag/stores/chroma.py +2 -2
  20. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/rag/stores/memory.py +10 -11
  21. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/rag/vector_store.py +7 -5
  22. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/serve/api.py +7 -5
  23. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/serve/app.py +34 -12
  24. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/sessions.py +60 -34
  25. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/templates/__init__.py +2 -9
  26. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/browser_tools.py +7 -0
  27. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/code_tools.py +53 -16
  28. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/image_tools.py +3 -1
  29. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/search_tools.py +1 -51
  30. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/types.py +17 -4
  31. {selectools-0.27.1 → selectools-0.28.0/src/selectools.egg-info}/PKG-INFO +30 -7
  32. {selectools-0.27.1 → selectools-0.28.0}/src/selectools.egg-info/SOURCES.txt +2 -0
  33. {selectools-0.27.1 → selectools-0.28.0}/src/selectools.egg-info/requires.txt +6 -1
  34. {selectools-0.27.1 → selectools-0.28.0}/tests/test_conversation_branching.py +40 -0
  35. {selectools-0.27.1 → selectools-0.28.0}/tests/test_guardrails_injection.py +18 -0
  36. {selectools-0.27.1 → selectools-0.28.0}/tests/test_model_switching.py +46 -0
  37. {selectools-0.27.1 → selectools-0.28.0}/tests/test_scheduler.py +31 -6
  38. {selectools-0.27.1 → selectools-0.28.0}/tests/test_sessions_dynamodb.py +32 -0
  39. selectools-0.28.0/tests/test_ssrf.py +63 -0
  40. {selectools-0.27.1 → selectools-0.28.0}/tests/test_yaml_config.py +4 -2
  41. {selectools-0.27.1 → selectools-0.28.0}/LICENSE +0 -0
  42. {selectools-0.27.1 → selectools-0.28.0}/setup.cfg +0 -0
  43. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/_async_utils.py +0 -0
  44. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/_json_utils.py +0 -0
  45. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/a2a/__init__.py +0 -0
  46. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/a2a/client.py +0 -0
  47. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/a2a/server.py +0 -0
  48. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/a2a/types.py +0 -0
  49. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/agent/__init__.py +0 -0
  50. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/agent/_lifecycle.py +0 -0
  51. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/agent/_memory_manager.py +0 -0
  52. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/agent/_planning.py +0 -0
  53. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/agent/config_groups.py +0 -0
  54. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/analytics.py +0 -0
  55. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/audit.py +0 -0
  56. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/cache.py +0 -0
  57. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/cache_redis.py +0 -0
  58. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/cache_semantic.py +0 -0
  59. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/cancellation.py +0 -0
  60. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/checkpoint_postgres.py +0 -0
  61. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/coherence.py +0 -0
  62. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/compose.py +0 -0
  63. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/embeddings/__init__.py +0 -0
  64. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/embeddings/gemini.py +0 -0
  65. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/embeddings/provider.py +0 -0
  66. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/entity_memory.py +0 -0
  67. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/env.py +0 -0
  68. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/evals/__init__.py +0 -0
  69. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/evals/__main__.py +0 -0
  70. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/evals/badge.py +0 -0
  71. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/evals/dataset.py +0 -0
  72. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/evals/generator.py +0 -0
  73. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/evals/history.py +0 -0
  74. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/evals/html.py +0 -0
  75. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/evals/junit.py +0 -0
  76. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/evals/llm_evaluators.py +0 -0
  77. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/evals/pairwise.py +0 -0
  78. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/evals/regression.py +0 -0
  79. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/evals/report.py +0 -0
  80. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/evals/serve.py +0 -0
  81. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/evals/snapshot.py +0 -0
  82. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/evals/suite.py +0 -0
  83. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/evals/templates.py +0 -0
  84. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/evals/types.py +0 -0
  85. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/exceptions.py +0 -0
  86. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/guardrails/__init__.py +0 -0
  87. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/guardrails/base.py +0 -0
  88. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/guardrails/format.py +0 -0
  89. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/guardrails/injection.py +0 -0
  90. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/guardrails/length.py +0 -0
  91. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/guardrails/pii.py +0 -0
  92. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/guardrails/pipeline.py +0 -0
  93. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/guardrails/topic.py +0 -0
  94. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/guardrails/toxicity.py +0 -0
  95. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/knowledge.py +0 -0
  96. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/knowledge_backends.py +0 -0
  97. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/knowledge_graph.py +0 -0
  98. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/knowledge_sanitizers.py +0 -0
  99. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/knowledge_store_redis.py +0 -0
  100. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/knowledge_store_supabase.py +0 -0
  101. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/loop_detection.py +0 -0
  102. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/mcp/__init__.py +0 -0
  103. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/mcp/_loop.py +0 -0
  104. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/mcp/bridge.py +0 -0
  105. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/mcp/client.py +0 -0
  106. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/mcp/config.py +0 -0
  107. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/mcp/server.py +0 -0
  108. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/memory.py +0 -0
  109. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/models.py +0 -0
  110. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/observe/__init__.py +0 -0
  111. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/observe/langfuse.py +0 -0
  112. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/observe/otel.py +0 -0
  113. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/observe/trace_store.py +0 -0
  114. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/observer.py +0 -0
  115. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/orchestration/__init__.py +0 -0
  116. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/orchestration/checkpoint.py +0 -0
  117. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/orchestration/node.py +0 -0
  118. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/orchestration/state.py +0 -0
  119. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/parser.py +0 -0
  120. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/patterns/__init__.py +0 -0
  121. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/patterns/debate.py +0 -0
  122. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/patterns/plan_and_execute.py +0 -0
  123. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/patterns/reflective.py +0 -0
  124. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/patterns/team_lead.py +0 -0
  125. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/pending.py +0 -0
  126. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/pipeline.py +0 -0
  127. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/policy.py +0 -0
  128. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/pricing.py +0 -0
  129. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/prompt.py +0 -0
  130. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/providers/__init__.py +0 -0
  131. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/providers/_openai_compat.py +0 -0
  132. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/providers/azure_openai_provider.py +0 -0
  133. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/providers/base.py +0 -0
  134. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/providers/fallback.py +0 -0
  135. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/providers/gemini_provider.py +0 -0
  136. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/providers/litellm_provider.py +0 -0
  137. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/providers/ollama_provider.py +0 -0
  138. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/providers/openai_provider.py +0 -0
  139. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/providers/router.py +0 -0
  140. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/providers/stubs.py +0 -0
  141. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/rag/__init__.py +0 -0
  142. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/rag/bm25.py +0 -0
  143. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/rag/chunking.py +0 -0
  144. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/rag/hybrid.py +0 -0
  145. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/rag/reranker.py +0 -0
  146. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/rag/stores/__init__.py +0 -0
  147. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/rag/stores/faiss.py +0 -0
  148. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/rag/stores/pgvector.py +0 -0
  149. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/rag/stores/pinecone.py +0 -0
  150. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/rag/stores/qdrant.py +0 -0
  151. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/rag/stores/sqlite.py +0 -0
  152. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/rag/tools.py +0 -0
  153. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/results.py +0 -0
  154. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/scheduler.py +0 -0
  155. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/security.py +0 -0
  156. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/serve/__init__.py +0 -0
  157. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/serve/_starlette_app.py +0 -0
  158. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/serve/_static/builder.css +0 -0
  159. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/serve/_static/builder.html +0 -0
  160. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/serve/_static/builder.js +0 -0
  161. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/serve/builder.py +0 -0
  162. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/serve/cli.py +0 -0
  163. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/serve/models.py +0 -0
  164. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/serve/playground.py +0 -0
  165. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/stability.py +0 -0
  166. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/structured.py +0 -0
  167. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/templates/code_reviewer.py +0 -0
  168. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/templates/customer_support.py +0 -0
  169. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/templates/data_analyst.py +0 -0
  170. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/templates/rag_chatbot.py +0 -0
  171. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/templates/research_assistant.py +0 -0
  172. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/token_estimation.py +0 -0
  173. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/__init__.py +0 -0
  174. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/calculator_tools.py +0 -0
  175. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/data_tools.py +0 -0
  176. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/datetime_tools.py +0 -0
  177. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/db_tools.py +0 -0
  178. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/discord_tools.py +0 -0
  179. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/email_tools.py +0 -0
  180. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/file_tools.py +0 -0
  181. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/github_tools.py +0 -0
  182. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/linear_tools.py +0 -0
  183. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/memory_tools.py +0 -0
  184. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/notion_tools.py +0 -0
  185. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/pdf_tools.py +0 -0
  186. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/reasoning_tools.py +0 -0
  187. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/s3_tools.py +0 -0
  188. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/slack_tools.py +0 -0
  189. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/text_tools.py +0 -0
  190. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/toolbox/web_tools.py +0 -0
  191. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/tools/__init__.py +0 -0
  192. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/tools/base.py +0 -0
  193. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/tools/decorators.py +0 -0
  194. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/tools/loader.py +0 -0
  195. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/tools/registry.py +0 -0
  196. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/trace.py +0 -0
  197. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/unified_memory.py +0 -0
  198. {selectools-0.27.1 → selectools-0.28.0}/src/selectools/usage.py +0 -0
  199. {selectools-0.27.1 → selectools-0.28.0}/src/selectools.egg-info/dependency_links.txt +0 -0
  200. {selectools-0.27.1 → selectools-0.28.0}/src/selectools.egg-info/entry_points.txt +0 -0
  201. {selectools-0.27.1 → selectools-0.28.0}/src/selectools.egg-info/top_level.txt +0 -0
  202. {selectools-0.27.1 → selectools-0.28.0}/tests/test_a2a.py +0 -0
  203. {selectools-0.27.1 → selectools-0.28.0}/tests/test_agent_api.py +0 -0
  204. {selectools-0.27.1 → selectools-0.28.0}/tests/test_agent_hitl.py +0 -0
  205. {selectools-0.27.1 → selectools-0.28.0}/tests/test_approval_gate.py +0 -0
  206. {selectools-0.27.1 → selectools-0.28.0}/tests/test_architecture.py +0 -0
  207. {selectools-0.27.1 → selectools-0.28.0}/tests/test_async_observers.py +0 -0
  208. {selectools-0.27.1 → selectools-0.28.0}/tests/test_audit.py +0 -0
  209. {selectools-0.27.1 → selectools-0.28.0}/tests/test_bake_window_hunt.py +0 -0
  210. {selectools-0.27.1 → selectools-0.28.0}/tests/test_budget.py +0 -0
  211. {selectools-0.27.1 → selectools-0.28.0}/tests/test_bug_hunt_batch1_core.py +0 -0
  212. {selectools-0.27.1 → selectools-0.28.0}/tests/test_bug_hunt_batch1_security.py +0 -0
  213. {selectools-0.27.1 → selectools-0.28.0}/tests/test_bug_hunt_batch1_tools.py +0 -0
  214. {selectools-0.27.1 → selectools-0.28.0}/tests/test_bug_hunt_regression.py +0 -0
  215. {selectools-0.27.1 → selectools-0.28.0}/tests/test_cache.py +0 -0
  216. {selectools-0.27.1 → selectools-0.28.0}/tests/test_cache_redis.py +0 -0
  217. {selectools-0.27.1 → selectools-0.28.0}/tests/test_cancellation.py +0 -0
  218. {selectools-0.27.1 → selectools-0.28.0}/tests/test_checkpoint_postgres.py +0 -0
  219. {selectools-0.27.1 → selectools-0.28.0}/tests/test_coherence.py +0 -0
  220. {selectools-0.27.1 → selectools-0.28.0}/tests/test_concurrency_smoke.py +0 -0
  221. {selectools-0.27.1 → selectools-0.28.0}/tests/test_consolidation_regression.py +0 -0
  222. {selectools-0.27.1 → selectools-0.28.0}/tests/test_coverage_orchestration.py +0 -0
  223. {selectools-0.27.1 → selectools-0.28.0}/tests/test_document_loaders_extended.py +0 -0
  224. {selectools-0.27.1 → selectools-0.28.0}/tests/test_e2e_langfuse_observer.py +0 -0
  225. {selectools-0.27.1 → selectools-0.28.0}/tests/test_e2e_multimodal.py +0 -0
  226. {selectools-0.27.1 → selectools-0.28.0}/tests/test_e2e_otel_observer.py +0 -0
  227. {selectools-0.27.1 → selectools-0.28.0}/tests/test_e2e_v0_21_0_apps.py +0 -0
  228. {selectools-0.27.1 → selectools-0.28.0}/tests/test_e2e_v0_21_0_simulations.py +0 -0
  229. {selectools-0.27.1 → selectools-0.28.0}/tests/test_entity_memory.py +0 -0
  230. {selectools-0.27.1 → selectools-0.28.0}/tests/test_env.py +0 -0
  231. {selectools-0.27.1 → selectools-0.28.0}/tests/test_evals.py +0 -0
  232. {selectools-0.27.1 → selectools-0.28.0}/tests/test_evals_advanced.py +0 -0
  233. {selectools-0.27.1 → selectools-0.28.0}/tests/test_evals_cli.py +0 -0
  234. {selectools-0.27.1 → selectools-0.28.0}/tests/test_evals_e2e.py +0 -0
  235. {selectools-0.27.1 → selectools-0.28.0}/tests/test_evals_final.py +0 -0
  236. {selectools-0.27.1 → selectools-0.28.0}/tests/test_evals_hardening.py +0 -0
  237. {selectools-0.27.1 → selectools-0.28.0}/tests/test_evals_html_report.py +0 -0
  238. {selectools-0.27.1 → selectools-0.28.0}/tests/test_evals_new_evaluators.py +0 -0
  239. {selectools-0.27.1 → selectools-0.28.0}/tests/test_evals_ralph_bugs.py +0 -0
  240. {selectools-0.27.1 → selectools-0.28.0}/tests/test_evals_release.py +0 -0
  241. {selectools-0.27.1 → selectools-0.28.0}/tests/test_evals_serve.py +0 -0
  242. {selectools-0.27.1 → selectools-0.28.0}/tests/test_evals_v017_features.py +0 -0
  243. {selectools-0.27.1 → selectools-0.28.0}/tests/test_evals_v0191.py +0 -0
  244. {selectools-0.27.1 → selectools-0.28.0}/tests/test_features_in_graph.py +0 -0
  245. {selectools-0.27.1 → selectools-0.28.0}/tests/test_final_coverage_a.py +0 -0
  246. {selectools-0.27.1 → selectools-0.28.0}/tests/test_final_coverage_b.py +0 -0
  247. {selectools-0.27.1 → selectools-0.28.0}/tests/test_guardrails.py +0 -0
  248. {selectools-0.27.1 → selectools-0.28.0}/tests/test_hardening.py +0 -0
  249. {selectools-0.27.1 → selectools-0.28.0}/tests/test_knowledge.py +0 -0
  250. {selectools-0.27.1 → selectools-0.28.0}/tests/test_knowledge_backend.py +0 -0
  251. {selectools-0.27.1 → selectools-0.28.0}/tests/test_knowledge_backend_redis.py +0 -0
  252. {selectools-0.27.1 → selectools-0.28.0}/tests/test_knowledge_backend_supabase.py +0 -0
  253. {selectools-0.27.1 → selectools-0.28.0}/tests/test_knowledge_graph.py +0 -0
  254. {selectools-0.27.1 → selectools-0.28.0}/tests/test_knowledge_sanitizers.py +0 -0
  255. {selectools-0.27.1 → selectools-0.28.0}/tests/test_knowledge_store_redis.py +0 -0
  256. {selectools-0.27.1 → selectools-0.28.0}/tests/test_knowledge_store_supabase.py +0 -0
  257. {selectools-0.27.1 → selectools-0.28.0}/tests/test_knowledge_stores.py +0 -0
  258. {selectools-0.27.1 → selectools-0.28.0}/tests/test_knowledge_stores_error_handling.py +0 -0
  259. {selectools-0.27.1 → selectools-0.28.0}/tests/test_langfuse_observer.py +0 -0
  260. {selectools-0.27.1 → selectools-0.28.0}/tests/test_loop_detection.py +0 -0
  261. {selectools-0.27.1 → selectools-0.28.0}/tests/test_mcp.py +0 -0
  262. {selectools-0.27.1 → selectools-0.28.0}/tests/test_mcp_coverage.py +0 -0
  263. {selectools-0.27.1 → selectools-0.28.0}/tests/test_memory.py +0 -0
  264. {selectools-0.27.1 → selectools-0.28.0}/tests/test_memory_async.py +0 -0
  265. {selectools-0.27.1 → selectools-0.28.0}/tests/test_memory_boundary.py +0 -0
  266. {selectools-0.27.1 → selectools-0.28.0}/tests/test_memory_integration.py +0 -0
  267. {selectools-0.27.1 → selectools-0.28.0}/tests/test_multi_agent_edge_cases.py +0 -0
  268. {selectools-0.27.1 → selectools-0.28.0}/tests/test_multimodal.py +0 -0
  269. {selectools-0.27.1 → selectools-0.28.0}/tests/test_orchestration_checkpoint.py +0 -0
  270. {selectools-0.27.1 → selectools-0.28.0}/tests/test_orchestration_e2e.py +0 -0
  271. {selectools-0.27.1 → selectools-0.28.0}/tests/test_orchestration_evals.py +0 -0
  272. {selectools-0.27.1 → selectools-0.28.0}/tests/test_orchestration_graph.py +0 -0
  273. {selectools-0.27.1 → selectools-0.28.0}/tests/test_orchestration_integration.py +0 -0
  274. {selectools-0.27.1 → selectools-0.28.0}/tests/test_orchestration_primitives.py +0 -0
  275. {selectools-0.27.1 → selectools-0.28.0}/tests/test_orchestration_supervisor.py +0 -0
  276. {selectools-0.27.1 → selectools-0.28.0}/tests/test_otel_observer.py +0 -0
  277. {selectools-0.27.1 → selectools-0.28.0}/tests/test_parser.py +0 -0
  278. {selectools-0.27.1 → selectools-0.28.0}/tests/test_patterns.py +0 -0
  279. {selectools-0.27.1 → selectools-0.28.0}/tests/test_pending.py +0 -0
  280. {selectools-0.27.1 → selectools-0.28.0}/tests/test_pending_redis_smoke.py +0 -0
  281. {selectools-0.27.1 → selectools-0.28.0}/tests/test_phase1_design_patterns.py +0 -0
  282. {selectools-0.27.1 → selectools-0.28.0}/tests/test_pipeline.py +0 -0
  283. {selectools-0.27.1 → selectools-0.28.0}/tests/test_pipeline_coverage.py +0 -0
  284. {selectools-0.27.1 → selectools-0.28.0}/tests/test_policy.py +0 -0
  285. {selectools-0.27.1 → selectools-0.28.0}/tests/test_prompt.py +0 -0
  286. {selectools-0.27.1 → selectools-0.28.0}/tests/test_prompt_compression.py +0 -0
  287. {selectools-0.27.1 → selectools-0.28.0}/tests/test_property_based.py +0 -0
  288. {selectools-0.27.1 → selectools-0.28.0}/tests/test_reasoning_strategy.py +0 -0
  289. {selectools-0.27.1 → selectools-0.28.0}/tests/test_remaining_coverage.py +0 -0
  290. {selectools-0.27.1 → selectools-0.28.0}/tests/test_results.py +0 -0
  291. {selectools-0.27.1 → selectools-0.28.0}/tests/test_routing_mode.py +0 -0
  292. {selectools-0.27.1 → selectools-0.28.0}/tests/test_security.py +0 -0
  293. {selectools-0.27.1 → selectools-0.28.0}/tests/test_semantic_cache.py +0 -0
  294. {selectools-0.27.1 → selectools-0.28.0}/tests/test_serve_app_coverage.py +0 -0
  295. {selectools-0.27.1 → selectools-0.28.0}/tests/test_serve_cli.py +0 -0
  296. {selectools-0.27.1 → selectools-0.28.0}/tests/test_sessions.py +0 -0
  297. {selectools-0.27.1 → selectools-0.28.0}/tests/test_sessions_edge_cases.py +0 -0
  298. {selectools-0.27.1 → selectools-0.28.0}/tests/test_sessions_mongo.py +0 -0
  299. {selectools-0.27.1 → selectools-0.28.0}/tests/test_sessions_redis.py +0 -0
  300. {selectools-0.27.1 → selectools-0.28.0}/tests/test_sessions_supabase.py +0 -0
  301. {selectools-0.27.1 → selectools-0.28.0}/tests/test_simple_observer.py +0 -0
  302. {selectools-0.27.1 → selectools-0.28.0}/tests/test_simulation_evals.py +0 -0
  303. {selectools-0.27.1 → selectools-0.28.0}/tests/test_stability.py +0 -0
  304. {selectools-0.27.1 → selectools-0.28.0}/tests/test_starlette_app.py +0 -0
  305. {selectools-0.27.1 → selectools-0.28.0}/tests/test_structured.py +0 -0
  306. {selectools-0.27.1 → selectools-0.28.0}/tests/test_structured_config.py +0 -0
  307. {selectools-0.27.1 → selectools-0.28.0}/tests/test_structured_tool_results.py +0 -0
  308. {selectools-0.27.1 → selectools-0.28.0}/tests/test_summarize_on_trim.py +0 -0
  309. {selectools-0.27.1 → selectools-0.28.0}/tests/test_templates_coverage.py +0 -0
  310. {selectools-0.27.1 → selectools-0.28.0}/tests/test_terminal_actions.py +0 -0
  311. {selectools-0.27.1 → selectools-0.28.0}/tests/test_token_estimation.py +0 -0
  312. {selectools-0.27.1 → selectools-0.28.0}/tests/test_tool_caching.py +0 -0
  313. {selectools-0.27.1 → selectools-0.28.0}/tests/test_tool_result_compression.py +0 -0
  314. {selectools-0.27.1 → selectools-0.28.0}/tests/test_trace.py +0 -0
  315. {selectools-0.27.1 → selectools-0.28.0}/tests/test_trace_html.py +0 -0
  316. {selectools-0.27.1 → selectools-0.28.0}/tests/test_trace_store.py +0 -0
  317. {selectools-0.27.1 → selectools-0.28.0}/tests/test_unified_memory.py +0 -0
  318. {selectools-0.27.1 → selectools-0.28.0}/tests/test_unified_memory_config.py +0 -0
  319. {selectools-0.27.1 → selectools-0.28.0}/tests/test_v016_regression.py +0 -0
  320. {selectools-0.27.1 → selectools-0.28.0}/tests/test_v019_features.py +0 -0
  321. {selectools-0.27.1 → selectools-0.28.0}/tests/test_visual_builder.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: selectools
3
- Version: 0.27.1
3
+ Version: 0.28.0
4
4
  Summary: Production-ready Python framework for AI agents with multi-agent graphs, hybrid RAG, guardrails, audit logging, 50 evaluators, and a visual builder. Supports OpenAI, Anthropic, Gemini, Ollama. By NichevLabs.
5
5
  Author-email: John Nichev <johnnichev@gmail.com>
6
6
  Maintainer-email: NichevLabs <support@nichevlabs.com>
@@ -59,14 +59,18 @@ Provides-Extra: toolbox
59
59
  Requires-Dist: requests>=2.28.0; extra == "toolbox"
60
60
  Requires-Dist: slack-sdk>=3.27.0; extra == "toolbox"
61
61
  Requires-Dist: pdfplumber>=0.11.0; extra == "toolbox"
62
+ Requires-Dist: pytz>=2023.3; extra == "toolbox"
62
63
  Provides-Extra: aws
63
64
  Requires-Dist: boto3>=1.34.0; extra == "aws"
64
65
  Provides-Extra: browser
65
66
  Requires-Dist: playwright>=1.40.0; extra == "browser"
66
67
  Provides-Extra: mongo
67
68
  Requires-Dist: pymongo>=4.0.0; extra == "mongo"
69
+ Provides-Extra: cache
70
+ Requires-Dist: redis>=4.0.0; extra == "cache"
68
71
  Provides-Extra: evals
69
72
  Requires-Dist: pyyaml>=6.0.0; extra == "evals"
73
+ Requires-Dist: jsonschema>=4.0.0; extra == "evals"
70
74
  Provides-Extra: mcp
71
75
  Requires-Dist: mcp<2.0.0,>=1.0.0; extra == "mcp"
72
76
  Provides-Extra: litellm
@@ -77,7 +81,7 @@ Provides-Extra: supabase
77
81
  Requires-Dist: supabase>=2.0.0; extra == "supabase"
78
82
  Provides-Extra: serve
79
83
  Requires-Dist: pyyaml>=6.0.0; extra == "serve"
80
- Requires-Dist: starlette>=0.27.0; extra == "serve"
84
+ Requires-Dist: starlette<1.0,>=0.27.0; extra == "serve"
81
85
  Requires-Dist: uvicorn[standard]>=0.24.0; extra == "serve"
82
86
  Requires-Dist: httpx>=0.24.0; extra == "serve"
83
87
  Dynamic: license-file
@@ -114,8 +118,27 @@ result = AgentGraph.chain(planner, writer, reviewer).run("Write a blog post")
114
118
  # selectools serve agent.yaml
115
119
  ```
116
120
 
121
+ ## What's New in v0.28
122
+
123
+ ### v0.28.0 — Hardening Sweep: Shell Safety, Session Namespaces & Typed Public API
124
+
125
+ A full audit-driven tech-debt sweep — mostly additive and bugfix, with one called-out behavior change.
126
+
127
+ - **`execute_shell` is now a real boundary, not a best-effort filter** — it parses with `shlex` and runs with `shell=False`, so pipes, chaining (`;`, `&&`), redirection, subshells, globbing, and backgrounding can never be interpreted (closes the previously-bypassable `\n` and bare-`&` holes). **Behavior change:** commands relying on shell features now fail fast instead of running through `/bin/sh`.
128
+ - **SSRF guard extended to the headless-browser tools and the serve webhook** — `browser_scrape_page`/`browser_screenshot` and the eval-alert webhook now reject loopback/private/link-local targets; SSRF logic is consolidated into one shared validator.
129
+ - **Session namespaces fixed across all backends** — `SessionStore.branch()` gained an optional `namespace` parameter, and `list()` now returns a round-trippable storage key consistently (JSON/Redis/Mongo/DynamoDB previously returned a bare id that couldn't be reloaded for namespaced sessions).
130
+ - **Embedding providers accept `timeout` + `max_retries`** (OpenAI/Voyage/Cohere, default 60s/2) so a hung or rate-limited call can't block ingestion.
131
+ - **Typed public API** — `AgentResult.trace`/`.usage` and the ten `AgentConfig` nested-group fields are now their concrete types instead of `Any` (full autocomplete + type-checking); tightening surfaced and fixed two real bugs the `Any` had masked.
132
+ - **Packaging** — added the missing `[cache]` extra (`redis`), `pytz` to `[toolbox]`, and `jsonschema` to `[evals]`.
133
+
134
+ See `CHANGELOG.md` for the full entry (7,796 tests, 115 examples, 115 models).
135
+
117
136
  ## What's New in v0.27
118
137
 
138
+ ### v0.27.2 — Provider Model Fix & Tool-less Agents
139
+
140
+ Fixes a P0: the agent ignored every provider's default model and sent `gpt-5-mini` to all of them, so **any non-OpenAI provider (Anthropic, Gemini, Ollama, LiteLLM) 404'd on every request**. `AgentConfig.model` now defaults to `None` and resolves to the provider's own default. Also: `Agent(tools=[])` is now valid (pure conversational agents), and the structured-output (`result.parsed` vs `.content`) and streaming (accumulate `StreamChunk` deltas, not the terminal `AgentResult`) sharp edges are documented.
141
+
119
142
  ### v0.27.1 — Bug-Hunt Patch
120
143
 
121
144
  An adversarial bug hunt of the v0.27.0 surface fixed 10 confirmed bugs (no API changes, no breaking changes). Highlights: scheduled agents recorded the `AgentResult` repr instead of the answer text; `PromptInjectionGuardrail` blocked benign requests ("pretend you are a pirate", "show the rules"); `s3_get_object` leaked its HTTP connection; and unified memory persisted un-redacted input in async mode. See `CHANGELOG.md` for the full list.
@@ -294,7 +317,7 @@ The first AI agent framework to ship a visual graph builder in a single `pip ins
294
317
 
295
318
  **[Try the builder in your browser →](https://selectools.dev/builder/)** — no install required.
296
319
 
297
- [![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/johnnichev/selectools/blob/main/notebooks/getting_started.ipynb) [![Examples Gallery](https://img.shields.io/badge/examples-96_scripts-06b6d4)](https://selectools.dev/examples/)
320
+ [![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/johnnichev/selectools/blob/main/notebooks/getting_started.ipynb) [![Examples Gallery](https://img.shields.io/badge/examples-115_scripts-06b6d4)](https://selectools.dev/examples/)
298
321
 
299
322
  ```bash
300
323
  pip install selectools
@@ -596,7 +619,7 @@ report = suite.run()
596
619
  report.to_html("report.html")
597
620
  ```
598
621
 
599
- - **50 Evaluators** — 30 deterministic + 21 LLM-as-judge
622
+ - **50 Evaluators** — 29 deterministic + 21 LLM-as-judge
600
623
  - **A/B Testing**, regression detection, snapshot testing
601
624
  - **HTML reports**, JUnit XML, CLI, GitHub Action integration
602
625
 
@@ -678,7 +701,7 @@ report.to_html("report.html")
678
701
  | **Knowledge Graph** | Relationship triple extraction with in-memory and SQLite storage and keyword-based querying. |
679
702
  | **Cross-Session Knowledge** | Daily logs + persistent facts with auto-registered `remember` + `recall` tools. |
680
703
  | **MCP Integration** | Connect to any MCP tool server (stdio + HTTP). MCPClient, MultiMCPClient, MCPServer. Circuit breaker, retry, graceful degradation. |
681
- | **Eval Framework** | 50 built-in evaluators (30 deterministic + 21 LLM-as-judge). A/B testing, regression detection, snapshot testing, HTML reports, JUnit XML, CI integration. |
704
+ | **Eval Framework** | 50 built-in evaluators (29 deterministic + 21 LLM-as-judge). A/B testing, regression detection, snapshot testing, HTML reports, JUnit XML, CI integration. |
682
705
  | **Multi-Agent Orchestration** | `AgentGraph` for directed agent graphs, `SupervisorAgent` with 4 strategies, HITL via generator nodes, parallel execution, checkpointing, subgraph composition. |
683
706
  | **Composable Pipelines** | `Pipeline` + `@step` + `|` operator + `parallel()` + `branch()` — chain agents, tools, and transforms with plain Python. |
684
707
  | **AgentObserver Protocol** | 46-event lifecycle observer with `run_id`/`call_id` correlation. Built-in `LoggingObserver` + `SimpleStepObserver`. |
@@ -725,7 +748,7 @@ report.to_html("report.html")
725
748
  - **Multi-Agent Orchestration**: `AgentGraph` with routing, parallel execution, HITL, checkpointing; `SupervisorAgent` with 4 strategies (plan_and_execute, round_robin, dynamic, magentic)
726
749
  - **Composable Pipelines**: `Pipeline` + `@step` + `|` operator + `parallel()` + `branch()` — chain agents, tools, and transforms
727
750
  - **115 Examples**: Multi-agent graphs, RAG, hybrid search, streaming, structured output, traces, batch, policy, observer, guardrails, audit, sessions (incl. Supabase), entity memory, knowledge graph, eval framework, advanced agent patterns, stability markers, HTML trace viewer, agent-as-API, A2A, routing, unified memory, scheduled agents, reasoning tools, and more
728
- - **Built-in Eval Framework**: 50 evaluators (30 deterministic + 21 LLM-as-judge), A/B testing, regression detection, HTML reports, JUnit XML, snapshot testing
751
+ - **Built-in Eval Framework**: 50 evaluators (29 deterministic + 21 LLM-as-judge), A/B testing, regression detection, HTML reports, JUnit XML, snapshot testing
729
752
  - **AgentObserver Protocol**: 46 lifecycle events with `run_id` correlation, `LoggingObserver`, `SimpleStepObserver`, OTel export
730
753
  - **7700+ Tests**: Unit, integration, regression, and E2E with real API calls
731
754
 
@@ -1353,7 +1376,7 @@ pytest tests/ -x -q # All tests
1353
1376
  pytest tests/ -k "not e2e" # Skip E2E (no API keys needed)
1354
1377
  ```
1355
1378
 
1356
- 7268 tests covering parsing, agent loop, providers, RAG pipeline, hybrid search, advanced chunking, dynamic tools, caching, streaming, guardrails, sessions, memory, eval framework, budget/cancellation, knowledge stores, orchestration, pipelines, agent patterns, stability markers, trace viewer, serve API, A2A, routing, and E2E integration with real API calls.
1379
+ 7,796 tests covering parsing, agent loop, providers, RAG pipeline, hybrid search, advanced chunking, dynamic tools, caching, streaming, guardrails, sessions, memory, eval framework, budget/cancellation, knowledge stores, orchestration, pipelines, agent patterns, stability markers, trace viewer, serve API, A2A, routing, and E2E integration with real API calls.
1357
1380
 
1358
1381
  ## License
1359
1382
 
@@ -30,8 +30,27 @@ result = AgentGraph.chain(planner, writer, reviewer).run("Write a blog post")
30
30
  # selectools serve agent.yaml
31
31
  ```
32
32
 
33
+ ## What's New in v0.28
34
+
35
+ ### v0.28.0 — Hardening Sweep: Shell Safety, Session Namespaces & Typed Public API
36
+
37
+ A full audit-driven tech-debt sweep — mostly additive and bugfix, with one called-out behavior change.
38
+
39
+ - **`execute_shell` is now a real boundary, not a best-effort filter** — it parses with `shlex` and runs with `shell=False`, so pipes, chaining (`;`, `&&`), redirection, subshells, globbing, and backgrounding can never be interpreted (closes the previously-bypassable `\n` and bare-`&` holes). **Behavior change:** commands relying on shell features now fail fast instead of running through `/bin/sh`.
40
+ - **SSRF guard extended to the headless-browser tools and the serve webhook** — `browser_scrape_page`/`browser_screenshot` and the eval-alert webhook now reject loopback/private/link-local targets; SSRF logic is consolidated into one shared validator.
41
+ - **Session namespaces fixed across all backends** — `SessionStore.branch()` gained an optional `namespace` parameter, and `list()` now returns a round-trippable storage key consistently (JSON/Redis/Mongo/DynamoDB previously returned a bare id that couldn't be reloaded for namespaced sessions).
42
+ - **Embedding providers accept `timeout` + `max_retries`** (OpenAI/Voyage/Cohere, default 60s/2) so a hung or rate-limited call can't block ingestion.
43
+ - **Typed public API** — `AgentResult.trace`/`.usage` and the ten `AgentConfig` nested-group fields are now their concrete types instead of `Any` (full autocomplete + type-checking); tightening surfaced and fixed two real bugs the `Any` had masked.
44
+ - **Packaging** — added the missing `[cache]` extra (`redis`), `pytz` to `[toolbox]`, and `jsonschema` to `[evals]`.
45
+
46
+ See `CHANGELOG.md` for the full entry (7,796 tests, 115 examples, 115 models).
47
+
33
48
  ## What's New in v0.27
34
49
 
50
+ ### v0.27.2 — Provider Model Fix & Tool-less Agents
51
+
52
+ Fixes a P0: the agent ignored every provider's default model and sent `gpt-5-mini` to all of them, so **any non-OpenAI provider (Anthropic, Gemini, Ollama, LiteLLM) 404'd on every request**. `AgentConfig.model` now defaults to `None` and resolves to the provider's own default. Also: `Agent(tools=[])` is now valid (pure conversational agents), and the structured-output (`result.parsed` vs `.content`) and streaming (accumulate `StreamChunk` deltas, not the terminal `AgentResult`) sharp edges are documented.
53
+
35
54
  ### v0.27.1 — Bug-Hunt Patch
36
55
 
37
56
  An adversarial bug hunt of the v0.27.0 surface fixed 10 confirmed bugs (no API changes, no breaking changes). Highlights: scheduled agents recorded the `AgentResult` repr instead of the answer text; `PromptInjectionGuardrail` blocked benign requests ("pretend you are a pirate", "show the rules"); `s3_get_object` leaked its HTTP connection; and unified memory persisted un-redacted input in async mode. See `CHANGELOG.md` for the full list.
@@ -210,7 +229,7 @@ The first AI agent framework to ship a visual graph builder in a single `pip ins
210
229
 
211
230
  **[Try the builder in your browser →](https://selectools.dev/builder/)** — no install required.
212
231
 
213
- [![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/johnnichev/selectools/blob/main/notebooks/getting_started.ipynb) [![Examples Gallery](https://img.shields.io/badge/examples-96_scripts-06b6d4)](https://selectools.dev/examples/)
232
+ [![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/johnnichev/selectools/blob/main/notebooks/getting_started.ipynb) [![Examples Gallery](https://img.shields.io/badge/examples-115_scripts-06b6d4)](https://selectools.dev/examples/)
214
233
 
215
234
  ```bash
216
235
  pip install selectools
@@ -512,7 +531,7 @@ report = suite.run()
512
531
  report.to_html("report.html")
513
532
  ```
514
533
 
515
- - **50 Evaluators** — 30 deterministic + 21 LLM-as-judge
534
+ - **50 Evaluators** — 29 deterministic + 21 LLM-as-judge
516
535
  - **A/B Testing**, regression detection, snapshot testing
517
536
  - **HTML reports**, JUnit XML, CLI, GitHub Action integration
518
537
 
@@ -594,7 +613,7 @@ report.to_html("report.html")
594
613
  | **Knowledge Graph** | Relationship triple extraction with in-memory and SQLite storage and keyword-based querying. |
595
614
  | **Cross-Session Knowledge** | Daily logs + persistent facts with auto-registered `remember` + `recall` tools. |
596
615
  | **MCP Integration** | Connect to any MCP tool server (stdio + HTTP). MCPClient, MultiMCPClient, MCPServer. Circuit breaker, retry, graceful degradation. |
597
- | **Eval Framework** | 50 built-in evaluators (30 deterministic + 21 LLM-as-judge). A/B testing, regression detection, snapshot testing, HTML reports, JUnit XML, CI integration. |
616
+ | **Eval Framework** | 50 built-in evaluators (29 deterministic + 21 LLM-as-judge). A/B testing, regression detection, snapshot testing, HTML reports, JUnit XML, CI integration. |
598
617
  | **Multi-Agent Orchestration** | `AgentGraph` for directed agent graphs, `SupervisorAgent` with 4 strategies, HITL via generator nodes, parallel execution, checkpointing, subgraph composition. |
599
618
  | **Composable Pipelines** | `Pipeline` + `@step` + `|` operator + `parallel()` + `branch()` — chain agents, tools, and transforms with plain Python. |
600
619
  | **AgentObserver Protocol** | 46-event lifecycle observer with `run_id`/`call_id` correlation. Built-in `LoggingObserver` + `SimpleStepObserver`. |
@@ -641,7 +660,7 @@ report.to_html("report.html")
641
660
  - **Multi-Agent Orchestration**: `AgentGraph` with routing, parallel execution, HITL, checkpointing; `SupervisorAgent` with 4 strategies (plan_and_execute, round_robin, dynamic, magentic)
642
661
  - **Composable Pipelines**: `Pipeline` + `@step` + `|` operator + `parallel()` + `branch()` — chain agents, tools, and transforms
643
662
  - **115 Examples**: Multi-agent graphs, RAG, hybrid search, streaming, structured output, traces, batch, policy, observer, guardrails, audit, sessions (incl. Supabase), entity memory, knowledge graph, eval framework, advanced agent patterns, stability markers, HTML trace viewer, agent-as-API, A2A, routing, unified memory, scheduled agents, reasoning tools, and more
644
- - **Built-in Eval Framework**: 50 evaluators (30 deterministic + 21 LLM-as-judge), A/B testing, regression detection, HTML reports, JUnit XML, snapshot testing
663
+ - **Built-in Eval Framework**: 50 evaluators (29 deterministic + 21 LLM-as-judge), A/B testing, regression detection, HTML reports, JUnit XML, snapshot testing
645
664
  - **AgentObserver Protocol**: 46 lifecycle events with `run_id` correlation, `LoggingObserver`, `SimpleStepObserver`, OTel export
646
665
  - **7700+ Tests**: Unit, integration, regression, and E2E with real API calls
647
666
 
@@ -1269,7 +1288,7 @@ pytest tests/ -x -q # All tests
1269
1288
  pytest tests/ -k "not e2e" # Skip E2E (no API keys needed)
1270
1289
  ```
1271
1290
 
1272
- 7268 tests covering parsing, agent loop, providers, RAG pipeline, hybrid search, advanced chunking, dynamic tools, caching, streaming, guardrails, sessions, memory, eval framework, budget/cancellation, knowledge stores, orchestration, pipelines, agent patterns, stability markers, trace viewer, serve API, A2A, routing, and E2E integration with real API calls.
1291
+ 7,796 tests covering parsing, agent loop, providers, RAG pipeline, hybrid search, advanced chunking, dynamic tools, caching, streaming, guardrails, sessions, memory, eval framework, budget/cancellation, knowledge stores, orchestration, pipelines, agent patterns, stability markers, trace viewer, serve API, A2A, routing, and E2E integration with real API calls.
1273
1292
 
1274
1293
  ## License
1275
1294
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "selectools"
7
- version = "0.27.1"
7
+ version = "0.28.0"
8
8
  description = "Production-ready Python framework for AI agents with multi-agent graphs, hybrid RAG, guardrails, audit logging, 50 evaluators, and a visual builder. Supports OpenAI, Anthropic, Gemini, Ollama. By NichevLabs."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -79,6 +79,7 @@ toolbox = [
79
79
  "requests>=2.28.0",
80
80
  "slack-sdk>=3.27.0",
81
81
  "pdfplumber>=0.11.0",
82
+ "pytz>=2023.3",
82
83
  ]
83
84
  aws = [
84
85
  "boto3>=1.34.0",
@@ -89,8 +90,12 @@ browser = [
89
90
  mongo = [
90
91
  "pymongo>=4.0.0",
91
92
  ]
93
+ cache = [
94
+ "redis>=4.0.0",
95
+ ]
92
96
  evals = [
93
97
  "pyyaml>=6.0.0",
98
+ "jsonschema>=4.0.0",
94
99
  ]
95
100
  mcp = [
96
101
  "mcp>=1.0.0,<2.0.0",
@@ -106,7 +111,7 @@ supabase = [
106
111
  ]
107
112
  serve = [
108
113
  "pyyaml>=6.0.0",
109
- "starlette>=0.27.0",
114
+ "starlette>=0.27.0,<1.0",
110
115
  "uvicorn[standard]>=0.24.0",
111
116
  "httpx>=0.24.0",
112
117
  ]
@@ -1,6 +1,6 @@
1
1
  """Public exports for the selectools package."""
2
2
 
3
- __version__ = "0.27.1"
3
+ __version__ = "0.28.0"
4
4
 
5
5
  # Import submodules (lazy loading for optional dependencies)
6
6
  from . import embeddings, evals, guardrails, models, observe, patterns, rag, toolbox
@@ -0,0 +1,88 @@
1
+ """
2
+ Shared SSRF (server-side request forgery) URL validation.
3
+
4
+ A single source of truth for the blocked-network list and the
5
+ hostname-resolution check used by every outbound-request surface in the
6
+ library (web search/scrape tools, headless-browser tools, RAG document
7
+ loaders, and the serve webhook dispatcher).
8
+
9
+ Two entry points expose the same logic under the two contracts callers
10
+ need:
11
+
12
+ - :func:`validate_url` returns an error string (or ``None`` when safe), for
13
+ tool functions that report errors as their return value.
14
+ - :func:`check_url` raises :class:`ValueError`, for internal callers that
15
+ prefer exceptions.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import ipaddress
21
+ import socket
22
+ from typing import List, Optional, Union
23
+ from urllib.parse import urlparse
24
+
25
+ # Private / loopback / link-local networks that must never be reached from a
26
+ # user- or model-supplied URL.
27
+ _BLOCKED_NETWORKS: List[Union[ipaddress.IPv4Network, ipaddress.IPv6Network]] = [
28
+ ipaddress.ip_network("127.0.0.0/8"),
29
+ ipaddress.ip_network("10.0.0.0/8"),
30
+ ipaddress.ip_network("172.16.0.0/12"),
31
+ ipaddress.ip_network("192.168.0.0/16"),
32
+ ipaddress.ip_network("169.254.0.0/16"),
33
+ ipaddress.ip_network("::1/128"),
34
+ ipaddress.ip_network("fc00::/7"),
35
+ ipaddress.ip_network("fe80::/10"),
36
+ ]
37
+
38
+
39
+ def validate_url(url: str) -> Optional[str]:
40
+ """Validate *url* against SSRF rules.
41
+
42
+ Returns a human-readable error message if the URL uses a non-HTTP
43
+ scheme, has no hostname, targets a loopback/internal name, or resolves
44
+ to a private/reserved IP range. Returns ``None`` when the URL is safe to
45
+ request.
46
+ """
47
+ parsed = urlparse(url)
48
+
49
+ if parsed.scheme not in ("http", "https"):
50
+ return f"Error: URL scheme {parsed.scheme!r} is not allowed."
51
+
52
+ hostname = parsed.hostname
53
+ if not hostname:
54
+ return "Error: URL has no hostname."
55
+
56
+ lower_host = hostname.lower()
57
+ if lower_host in ("localhost", "0.0.0.0"): # nosec B104 — comparison, not a bind
58
+ return f"Error: Requests to {hostname!r} are blocked (loopback/internal address)."
59
+
60
+ try:
61
+ addr_infos = socket.getaddrinfo(hostname, None)
62
+ except socket.gaierror as e:
63
+ return f"Error: Could not resolve hostname {hostname!r}: {e}"
64
+
65
+ for _family, _type, _proto, _canonname, sockaddr in addr_infos:
66
+ ip = ipaddress.ip_address(sockaddr[0])
67
+ for network in _BLOCKED_NETWORKS:
68
+ if ip in network:
69
+ return (
70
+ f"Error: URL resolves to private/reserved address {ip} "
71
+ f"(network {network}). Requests to internal networks are blocked."
72
+ )
73
+
74
+ return None
75
+
76
+
77
+ def check_url(url: str) -> None:
78
+ """Raise :class:`ValueError` if *url* fails SSRF validation.
79
+
80
+ Equivalent to :func:`validate_url` but raises instead of returning the
81
+ message (with the ``"Error: "`` prefix stripped).
82
+ """
83
+ message = validate_url(url)
84
+ if message is not None:
85
+ raise ValueError(message.removeprefix("Error: "))
86
+
87
+
88
+ __all__ = ["validate_url", "check_url"]
@@ -6,7 +6,7 @@ import asyncio
6
6
  import threading
7
7
  import time
8
8
  from concurrent.futures import ThreadPoolExecutor
9
- from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple, cast
9
+ from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple
10
10
 
11
11
  # Module-level singleton for running sync provider calls in an async context.
12
12
  # Creating a new ThreadPoolExecutor per call (inside a retry loop) wastes
@@ -65,8 +65,15 @@ class _ProviderCallerMixin:
65
65
  temperature=self.config.temperature,
66
66
  )
67
67
  cached = self.config.cache.get(cache_key)
68
- if cached is not None:
69
- cached_msg = cast(Message, cached[0])
68
+ # Only treat a well-formed (Message, usage) pair as a cache hit;
69
+ # anything else (key collision, schema drift) falls through to a
70
+ # real provider call instead of crashing on a bad cast.
71
+ if (
72
+ isinstance(cached, (tuple, list))
73
+ and len(cached) >= 2
74
+ and isinstance(cached[0], Message)
75
+ ):
76
+ cached_msg = cached[0]
70
77
  cached_usage = cached[1]
71
78
  self.usage.add_usage(cached_usage, tool_name=None)
72
79
  if run_id:
@@ -273,8 +280,15 @@ class _ProviderCallerMixin:
273
280
  temperature=self.config.temperature,
274
281
  )
275
282
  cached = self.config.cache.get(cache_key)
276
- if cached is not None:
277
- cached_msg = cast(Message, cached[0])
283
+ # Only treat a well-formed (Message, usage) pair as a cache hit;
284
+ # anything else (key collision, schema drift) falls through to a
285
+ # real provider call instead of crashing on a bad cast.
286
+ if (
287
+ isinstance(cached, (tuple, list))
288
+ and len(cached) >= 2
289
+ and isinstance(cached[0], Message)
290
+ ):
291
+ cached_msg = cached[0]
278
292
  cached_usage = cached[1]
279
293
  self.usage.add_usage(cached_usage, tool_name=None)
280
294
  if run_id:
@@ -524,21 +538,29 @@ class _ProviderCallerMixin:
524
538
  elif isinstance(chunk, ToolCall):
525
539
  tool_calls.append(chunk)
526
540
  else:
527
- for chunk in self.provider.stream(
528
- model=self._effective_model,
529
- system_prompt=self._system_prompt,
530
- messages=self._history,
531
- tools=self.tools,
532
- temperature=self.config.temperature,
533
- max_tokens=self.config.max_tokens,
534
- timeout=self.config.request_timeout,
535
- ):
536
- if isinstance(chunk, str):
537
- if chunk:
538
- aggregated.append(chunk)
539
- if stream_handler:
540
- stream_handler(chunk)
541
- elif isinstance(chunk, ToolCall):
542
- tool_calls.append(chunk)
541
+ # Sync-generator fallback: wrap in closing() so a stream_handler
542
+ # exception or caller disconnect deterministically closes the
543
+ # provider generator (the sync analog of the aclosing path above).
544
+ from contextlib import closing
545
+
546
+ with closing(
547
+ self.provider.stream(
548
+ model=self._effective_model,
549
+ system_prompt=self._system_prompt,
550
+ messages=self._history,
551
+ tools=self.tools,
552
+ temperature=self.config.temperature,
553
+ max_tokens=self.config.max_tokens,
554
+ timeout=self.config.request_timeout,
555
+ )
556
+ ) as stream:
557
+ for chunk in stream:
558
+ if isinstance(chunk, str):
559
+ if chunk:
560
+ aggregated.append(chunk)
561
+ if stream_handler:
562
+ stream_handler(chunk)
563
+ elif isinstance(chunk, ToolCall):
564
+ tool_calls.append(chunk)
543
565
 
544
566
  return "".join(aggregated), tool_calls
@@ -107,7 +107,7 @@ class _ToolExecutorMixin:
107
107
  return False
108
108
  if tool is None or getattr(tool, "terminal", False):
109
109
  return False
110
- return len(result) > tcfg.compress_threshold
110
+ return bool(len(result) > tcfg.compress_threshold)
111
111
 
112
112
  def _stop_condition_hit(self, tool_name: str, result: str) -> bool:
113
113
  """True if config.stop_condition fires for this result (terminal path)."""
@@ -138,6 +138,7 @@ class _ToolExecutorMixin:
138
138
  tool_name,
139
139
  type(exc).__name__,
140
140
  exc,
141
+ exc_info=exc,
141
142
  )
142
143
 
143
144
  def _build_compression_request(self, tool_name: str, result: str) -> Message:
@@ -203,7 +204,7 @@ class _ToolExecutorMixin:
203
204
  def _compression_max_tokens(self) -> int:
204
205
  """Token budget for the summary: comfortably under the char threshold."""
205
206
  threshold = self.config.tool.compress_threshold
206
- return max(128, min(1000, threshold // 4))
207
+ return int(max(128, min(1000, threshold // 4)))
207
208
 
208
209
  def _compress_tool_result(
209
210
  self,
@@ -22,6 +22,18 @@ if TYPE_CHECKING:
22
22
  from ..providers.base import Provider
23
23
  from ..sessions import SessionStore
24
24
  from ..usage import AgentUsage
25
+ from .config_groups import (
26
+ BudgetConfig,
27
+ CoherenceConfig,
28
+ CompressConfig,
29
+ GuardrailsConfig,
30
+ MemoryConfig,
31
+ RetryConfig,
32
+ SessionConfig,
33
+ SummarizeConfig,
34
+ ToolConfig,
35
+ TraceConfig,
36
+ )
25
37
 
26
38
  ConfirmAction = Union[
27
39
  Callable[[str, Dict[str, Any], str], bool],
@@ -123,7 +135,10 @@ class AgentConfig:
123
135
  """
124
136
 
125
137
  name: str = "agent"
126
- model: str = "gpt-5-mini"
138
+ # None means "use the provider's own default_model". A hardcoded default
139
+ # here would be sent to EVERY provider — e.g. an OpenAI model id reaching
140
+ # the Anthropic API and 404-ing on every call.
141
+ model: Optional[str] = None
127
142
  temperature: float = 0.0
128
143
  max_tokens: int = 1000
129
144
  max_iterations: int = 6
@@ -176,16 +191,17 @@ class AgentConfig:
176
191
 
177
192
  # -- Nested config groups (optional, for YAML / clean APIs) --
178
193
  # When provided, nested values take precedence over flat fields.
179
- retry: Optional[Any] = None # RetryConfig
180
- tool: Optional[Any] = None # ToolConfig
181
- coherence: Optional[Any] = None # CoherenceConfig (name collision with coherence_check)
182
- guardrail: Optional[Any] = None # GuardrailsConfig
183
- session: Optional[Any] = None # SessionConfig
184
- summarize: Optional[Any] = None # SummarizeConfig
185
- memory: Optional[Any] = None # MemoryConfig
186
- budget: Optional[Any] = None # BudgetConfig
187
- trace: Optional[Any] = None # TraceConfig
188
- compress: Optional[Any] = None # CompressConfig
194
+ # (CoherenceConfig's attribute name collides with the flat coherence_check.)
195
+ retry: Optional["RetryConfig"] = None
196
+ tool: Optional["ToolConfig"] = None
197
+ coherence: Optional["CoherenceConfig"] = None
198
+ guardrail: Optional["GuardrailsConfig"] = None
199
+ session: Optional["SessionConfig"] = None
200
+ summarize: Optional["SummarizeConfig"] = None
201
+ memory: Optional["MemoryConfig"] = None
202
+ budget: Optional["BudgetConfig"] = None
203
+ trace: Optional["TraceConfig"] = None
204
+ compress: Optional["CompressConfig"] = None
189
205
 
190
206
  # -- Planning-as-config (ROADMAP P2). See PlanningConfig below and
191
207
  # agent/_planning.py for the adapter. Self-contained: default None means
@@ -142,16 +142,14 @@ class Agent(_ToolExecutorMixin, _ProviderCallerMixin, _LifecycleMixin, _MemoryMa
142
142
  Initialize a new Agent instance.
143
143
 
144
144
  Args:
145
- tools: List of Tool instances the agent can use (minimum 1 required).
145
+ tools: List of Tool instances the agent can use. May be empty for a
146
+ pure conversational agent (the provider is called with no tools).
146
147
  provider: LLM provider adapter. Defaults to OpenAIProvider().
147
148
  prompt_builder: Custom prompt builder. Defaults to PromptBuilder().
148
149
  parser: Custom tool call parser. Defaults to ToolCallParser().
149
150
  config: Agent configuration options. Defaults to AgentConfig().
150
151
  memory: Optional conversation memory for maintaining history across turns.
151
152
 
152
- Raises:
153
- ValueError: If tools list is empty.
154
-
155
153
  Example:
156
154
  >>> agent = Agent(
157
155
  ... tools=[search, calculator],
@@ -160,11 +158,10 @@ class Agent(_ToolExecutorMixin, _ProviderCallerMixin, _LifecycleMixin, _MemoryMa
160
158
  ... memory=ConversationMemory(max_messages=20)
161
159
  ... )
162
160
  """
163
- if not tools:
164
- raise ValueError("Agent requires at least one tool.")
165
-
166
- self.tools = tools
167
- self._tools_by_name = {tool.name: tool for tool in tools}
161
+ # An empty tool list is valid: a pure conversational agent (no tools)
162
+ # is a normal use case — the provider is simply called with no tools.
163
+ self.tools = tools or []
164
+ self._tools_by_name = {tool.name: tool for tool in self.tools}
168
165
  self.provider = provider or OpenAIProvider()
169
166
  strategy = config.reasoning_strategy if config else None
170
167
  if prompt_builder:
@@ -248,10 +245,16 @@ class Agent(_ToolExecutorMixin, _ProviderCallerMixin, _LifecycleMixin, _MemoryMa
248
245
  def _effective_model(self) -> str:
249
246
  """The model to use for the current iteration.
250
247
 
251
- Returns the model set by ``model_selector`` if active, otherwise
252
- falls back to ``self.config.model``.
248
+ Resolution order: a ``model_selector`` override, then an explicit
249
+ ``config.model``, then the provider's own ``default_model``. Without the
250
+ provider fallback a non-OpenAI provider would receive the OpenAI default
251
+ and 404. The final ``"gpt-5-mini"`` only applies to providers that expose
252
+ no default (e.g. the LocalProvider stub, which ignores the model).
253
253
  """
254
- return getattr(self, "_current_model", None) or self.config.model
254
+ override = getattr(self, "_current_model", None) or self.config.model
255
+ if override:
256
+ return override
257
+ return getattr(self.provider, "default_model", None) or "gpt-5-mini"
255
258
 
256
259
  def __call__(
257
260
  self,
@@ -316,14 +319,11 @@ class Agent(_ToolExecutorMixin, _ProviderCallerMixin, _LifecycleMixin, _MemoryMa
316
319
 
317
320
  Raises:
318
321
  KeyError: If no tool with that name exists.
319
- ValueError: If removing would leave the agent with zero tools.
320
322
  """
321
323
  if tool_name not in self._tools_by_name:
322
324
  raise KeyError(
323
325
  f"Tool '{tool_name}' not found. Available: {', '.join(self._tools_by_name.keys())}"
324
326
  )
325
- if len(self.tools) <= 1:
326
- raise ValueError("Agent requires at least one tool.")
327
327
 
328
328
  removed = self._tools_by_name.pop(tool_name)
329
329
  self.tools = [t for t in self.tools if t.name != tool_name]
@@ -1076,6 +1076,10 @@ class Agent(_ToolExecutorMixin, _ProviderCallerMixin, _LifecycleMixin, _MemoryMa
1076
1076
  result = AgentResult(
1077
1077
  message=Message(role=Role.ASSISTANT, content=f"Batch error: {exc}"),
1078
1078
  )
1079
+ # No lock needed here (unlike sync batch(), which uses real OS
1080
+ # threads): merge() and the increment are synchronous with no
1081
+ # await, so under asyncio's single-threaded event loop they run
1082
+ # atomically relative to the other gathered coroutines.
1079
1083
  self.usage.merge(clone.usage)
1080
1084
  completed += 1
1081
1085
  if on_progress:
@@ -1121,6 +1125,14 @@ class Agent(_ToolExecutorMixin, _ProviderCallerMixin, _LifecycleMixin, _MemoryMa
1121
1125
  Returns:
1122
1126
  AgentResult with the final response and tool call metadata.
1123
1127
 
1128
+ Error handling:
1129
+ If the provider keeps failing after ``config.max_retries`` attempts,
1130
+ the agent does NOT raise — it returns an ``AgentResult`` whose
1131
+ ``content`` is ``"Provider error: <detail>"`` (graceful degradation),
1132
+ and the failure is recorded on ``result.trace`` with the error.
1133
+ Check ``result.trace`` (or test for the ``"Provider error:"`` prefix)
1134
+ if you need to distinguish a provider failure from a real answer.
1135
+
1124
1136
  Examples:
1125
1137
  >>> result = agent.run("What is Python?")
1126
1138
  >>> result = agent.run([Message(role=Role.USER, content="Hello")])
@@ -1212,7 +1224,7 @@ class Agent(_ToolExecutorMixin, _ProviderCallerMixin, _LifecycleMixin, _MemoryMa
1212
1224
  # agent with a tight tool-iteration budget should
1213
1225
  # still allow the full RetryConfig.max_retries
1214
1226
  # worth of structured-validation retries.
1215
- retry_budget = self.config.retry.max_retries
1227
+ retry_budget = self.config.max_retries
1216
1228
  if ctx.structured_retries < retry_budget:
1217
1229
  ctx.structured_retries += 1
1218
1230
  ctx.trace.add(
@@ -1370,7 +1382,7 @@ class Agent(_ToolExecutorMixin, _ProviderCallerMixin, _LifecycleMixin, _MemoryMa
1370
1382
  clone.analytics = None
1371
1383
  return clone
1372
1384
 
1373
- @deprecated(since="1.0.0", replacement="clone_for_isolation")
1385
+ @deprecated(since="0.25.0", replacement="clone_for_isolation")
1374
1386
  def _clone_for_isolation(self) -> "Agent":
1375
1387
  """Deprecated alias for :meth:`clone_for_isolation`."""
1376
1388
  return self.clone_for_isolation()
@@ -1391,8 +1403,21 @@ class Agent(_ToolExecutorMixin, _ProviderCallerMixin, _LifecycleMixin, _MemoryMa
1391
1403
  parent_run_id: Optional run-ID of a parent agent for trace linking.
1392
1404
 
1393
1405
  Yields:
1394
- StreamChunk: Intermediate content chunks.
1395
- AgentResult: The final result object (yielded at the very end).
1406
+ StreamChunk: incremental content deltas (and tool-call chunks).
1407
+ AgentResult: the final, complete result yielded once at the very
1408
+ end. Its ``.content`` is the WHOLE answer, not a delta.
1409
+
1410
+ Consuming the stream — accumulate the ``StreamChunk`` deltas and treat
1411
+ the terminal ``AgentResult`` separately. Do NOT add the final
1412
+ ``AgentResult.content`` to the accumulated deltas, or you will get the
1413
+ answer twice::
1414
+
1415
+ text = ""
1416
+ async for chunk in agent.astream("..."):
1417
+ if isinstance(chunk, StreamChunk):
1418
+ text += chunk.content or "" # deltas only
1419
+ else: # AgentResult (terminal)
1420
+ result = chunk # full answer + metadata
1396
1421
  """
1397
1422
  messages = self._normalize_messages(messages)
1398
1423
 
@@ -1636,7 +1661,7 @@ class Agent(_ToolExecutorMixin, _ProviderCallerMixin, _LifecycleMixin, _MemoryMa
1636
1661
  # agent with a tight tool-iteration budget should
1637
1662
  # still allow the full RetryConfig.max_retries
1638
1663
  # worth of structured-validation retries.
1639
- retry_budget = self.config.retry.max_retries
1664
+ retry_budget = self.config.max_retries
1640
1665
  if ctx.structured_retries < retry_budget:
1641
1666
  ctx.structured_retries += 1
1642
1667
  ctx.trace.add(
@@ -1955,7 +1980,7 @@ class Agent(_ToolExecutorMixin, _ProviderCallerMixin, _LifecycleMixin, _MemoryMa
1955
1980
  # agent with a tight tool-iteration budget should
1956
1981
  # still allow the full RetryConfig.max_retries
1957
1982
  # worth of structured-validation retries.
1958
- retry_budget = self.config.retry.max_retries
1983
+ retry_budget = self.config.max_retries
1959
1984
  if ctx.structured_retries < retry_budget:
1960
1985
  ctx.structured_retries += 1
1961
1986
  ctx.trace.add(