adapt-agent 0.3.2__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/CHANGELOG.md +233 -1
  2. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/PKG-INFO +1 -1
  3. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/__init__.py +1 -1
  4. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/evaluation/__init__.py +9 -1
  5. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/optimization/__init__.py +9 -1
  6. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/optimization/introspection/claude_agent.py +95 -0
  7. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/optimization/introspection/crewai.py +62 -7
  8. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/optimization/introspection/google_adk.py +13 -3
  9. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/optimization/introspection/langgraph.py +10 -0
  10. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/optimization/introspection/microsoft_agent_framework.py +20 -0
  11. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/optimization/introspection/openai_agents.py +80 -7
  12. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/optimization/introspection/pydantic_ai.py +8 -1
  13. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/optimization/judge.py +44 -8
  14. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/optimization/optimizers.py +333 -113
  15. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/optimization/parameters.py +23 -3
  16. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/optimization/providers.py +15 -1
  17. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/optimization/runners.py +208 -9
  18. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/optimization/target.py +123 -17
  19. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent.egg-info/PKG-INFO +1 -1
  20. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/docs/evals.md +38 -5
  21. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/docs/optimization.md +116 -7
  22. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/claude_agent/03_evaluate_and_optimize.py +8 -4
  23. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/openai_agents/03_evaluate_and_optimize.py +6 -3
  24. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_optimize_introspect_claude_agent.py +104 -1
  25. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_optimize_introspect_crewai.py +93 -0
  26. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_optimize_introspect_google_adk.py +12 -0
  27. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_optimize_introspect_langgraph.py +15 -0
  28. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_optimize_introspect_microsoft_agent_framework.py +53 -0
  29. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_optimize_introspect_openai_agents.py +83 -1
  30. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_optimize_introspect_pydantic_ai.py +21 -0
  31. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_optimize_judge.py +25 -0
  32. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_optimize_optimizers.py +274 -0
  33. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_optimize_parameters.py +28 -0
  34. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_optimize_providers.py +39 -0
  35. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_optimize_runners.py +220 -0
  36. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_optimize_target.py +249 -0
  37. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/CODE_OF_CONDUCT.md +0 -0
  38. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/CONTRIBUTING.md +0 -0
  39. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/LICENSE +0 -0
  40. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/MANIFEST.in +0 -0
  41. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/README.md +0 -0
  42. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/SECURITY.md +0 -0
  43. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/__main__.py +0 -0
  44. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/adapters/__init__.py +0 -0
  45. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/adapters/_governed.py +0 -0
  46. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/adapters/base.py +0 -0
  47. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/adapters/claude_agent/__init__.py +0 -0
  48. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/adapters/crewai/__init__.py +0 -0
  49. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/adapters/google_adk/__init__.py +0 -0
  50. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/adapters/langgraph/__init__.py +0 -0
  51. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/adapters/microsoft_agent_framework/__init__.py +0 -0
  52. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/adapters/openai_agents/__init__.py +0 -0
  53. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/adapters/pydantic_ai/__init__.py +0 -0
  54. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/adversarial/__init__.py +0 -0
  55. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/cli/__init__.py +0 -0
  56. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/cli/__main__.py +0 -0
  57. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/core/__init__.py +0 -0
  58. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/core/governance.py +0 -0
  59. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/core/memory.py +0 -0
  60. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/core/middleware.py +0 -0
  61. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/core/policy.py +0 -0
  62. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/core/trust.py +0 -0
  63. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/core/types.py +0 -0
  64. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/exceptions.py +0 -0
  65. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/integrations/__init__.py +0 -0
  66. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/integrations/_common.py +0 -0
  67. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/integrations/agent_framework.py +0 -0
  68. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/integrations/claude_agent.py +0 -0
  69. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/integrations/crewai.py +0 -0
  70. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/integrations/google_adk.py +0 -0
  71. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/integrations/langgraph.py +0 -0
  72. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/integrations/openai_agents.py +0 -0
  73. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/integrations/pydantic_ai.py +0 -0
  74. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/observability/__init__.py +0 -0
  75. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/optimization/config.py +0 -0
  76. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/optimization/dataset.py +0 -0
  77. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/optimization/evals.py +0 -0
  78. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/optimization/evaluation.py +0 -0
  79. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/optimization/extractors.py +0 -0
  80. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/optimization/introspection/__init__.py +0 -0
  81. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/optimization/judges.py +0 -0
  82. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/optimization/metrics.py +0 -0
  83. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/optimization/performance.py +0 -0
  84. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/optimization/proposers.py +0 -0
  85. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/optimization/retry.py +0 -0
  86. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/patches/__init__.py +0 -0
  87. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/py.typed +0 -0
  88. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/security/__init__.py +0 -0
  89. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/security/firewall.py +0 -0
  90. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/security/taint_tracker.py +0 -0
  91. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/skills/__init__.py +0 -0
  92. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/skills/adapt-agent/SKILL.md +0 -0
  93. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/skills/adapt-agent/references/evals.md +0 -0
  94. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/skills/adapt-agent/references/guardrails.md +0 -0
  95. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent/skills/adapt-agent/references/optimization.md +0 -0
  96. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent.egg-info/SOURCES.txt +0 -0
  97. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent.egg-info/dependency_links.txt +0 -0
  98. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent.egg-info/entry_points.txt +0 -0
  99. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent.egg-info/requires.txt +0 -0
  100. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/adapt_agent.egg-info/top_level.txt +0 -0
  101. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/docs/adapters.md +0 -0
  102. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/docs/api.md +0 -0
  103. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/docs/cli.md +0 -0
  104. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/docs/frameworks/claude_agent.md +0 -0
  105. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/docs/frameworks/crewai.md +0 -0
  106. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/docs/frameworks/google_adk.md +0 -0
  107. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/docs/frameworks/index.md +0 -0
  108. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/docs/frameworks/langgraph.md +0 -0
  109. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/docs/frameworks/microsoft_agent_framework.md +0 -0
  110. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/docs/frameworks/openai_agents.md +0 -0
  111. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/docs/frameworks/pydantic_ai.md +0 -0
  112. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/docs/index.md +0 -0
  113. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/docs/integrations.md +0 -0
  114. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/docs/observability.md +0 -0
  115. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/docs/policy.md +0 -0
  116. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/docs/quickstart.md +0 -0
  117. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/docs/releasing.md +0 -0
  118. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/docs/security.md +0 -0
  119. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/docs/skill.md +0 -0
  120. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/01_firewall_and_policy.py +0 -0
  121. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/02_adversarial_defense.py +0 -0
  122. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/03_langgraph_guarded_agent.py +0 -0
  123. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/04_observability.py +0 -0
  124. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/05_multi_framework_adapters.py +0 -0
  125. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/06_optimize_with_golden_dataset.py +0 -0
  126. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/07_train_from_yaml.py +0 -0
  127. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/08_agent_evals.py +0 -0
  128. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/README.md +0 -0
  129. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/claude_agent/01_basic_guarded.py +0 -0
  130. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/claude_agent/02_policy_observability_trust.py +0 -0
  131. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/claude_agent/04_multi_agent_and_training.py +0 -0
  132. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/claude_agent/README.md +0 -0
  133. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/claude_agent/claude_agent.train.yaml +0 -0
  134. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/config.example.json +0 -0
  135. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/crewai/01_basic_guarded.py +0 -0
  136. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/crewai/02_policy_observability_trust.py +0 -0
  137. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/crewai/03_evaluate_and_optimize.py +0 -0
  138. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/crewai/04_multi_agent_and_training.py +0 -0
  139. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/crewai/README.md +0 -0
  140. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/crewai/crewai.train.yaml +0 -0
  141. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/google_adk/01_basic_guarded.py +0 -0
  142. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/google_adk/02_policy_observability_trust.py +0 -0
  143. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/google_adk/03_evaluate_and_optimize.py +0 -0
  144. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/google_adk/04_multi_agent_and_training.py +0 -0
  145. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/google_adk/README.md +0 -0
  146. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/google_adk/google_adk.train.yaml +0 -0
  147. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/langgraph/01_basic_guarded.py +0 -0
  148. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/langgraph/02_policy_observability_trust.py +0 -0
  149. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/langgraph/03_evaluate_and_optimize.py +0 -0
  150. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/langgraph/04_multi_agent_and_training.py +0 -0
  151. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/langgraph/README.md +0 -0
  152. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/langgraph/langgraph.train.yaml +0 -0
  153. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/microsoft_agent_framework/01_basic_guarded.py +0 -0
  154. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/microsoft_agent_framework/02_policy_observability_trust.py +0 -0
  155. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/microsoft_agent_framework/03_evaluate_and_optimize.py +0 -0
  156. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/microsoft_agent_framework/04_magentic_team_and_training.py +0 -0
  157. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/microsoft_agent_framework/README.md +0 -0
  158. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/microsoft_agent_framework/magentic.train.yaml +0 -0
  159. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/openai_agents/01_basic_guarded.py +0 -0
  160. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/openai_agents/02_policy_observability_trust.py +0 -0
  161. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/openai_agents/04_multi_agent_and_training.py +0 -0
  162. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/openai_agents/README.md +0 -0
  163. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/openai_agents/openai_agents.train.yaml +0 -0
  164. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/pydantic_ai/01_basic_guarded.py +0 -0
  165. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/pydantic_ai/02_policy_observability_trust.py +0 -0
  166. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/pydantic_ai/03_evaluate_and_optimize.py +0 -0
  167. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/pydantic_ai/04_multi_agent_and_training.py +0 -0
  168. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/pydantic_ai/README.md +0 -0
  169. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/pydantic_ai/pydantic_ai.train.yaml +0 -0
  170. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/examples/train.example.yaml +0 -0
  171. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/pyproject.toml +0 -0
  172. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/setup.cfg +0 -0
  173. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/__init__.py +0 -0
  174. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_action_refs.py +0 -0
  175. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_adapters.py +0 -0
  176. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_adversarial.py +0 -0
  177. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_adversarial_extra.py +0 -0
  178. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_async_governance.py +0 -0
  179. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_cli.py +0 -0
  180. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_cli_optimize.py +0 -0
  181. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_cli_skills.py +0 -0
  182. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_core.py +0 -0
  183. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_evaluation.py +0 -0
  184. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_exceptions.py +0 -0
  185. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_firewall_extra.py +0 -0
  186. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_governed_adapters.py +0 -0
  187. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_integrations.py +0 -0
  188. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_memory.py +0 -0
  189. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_middleware.py +0 -0
  190. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_observability.py +0 -0
  191. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_observability_extra.py +0 -0
  192. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_optimization.py +0 -0
  193. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_optimize_config.py +0 -0
  194. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_optimize_dataset.py +0 -0
  195. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_optimize_evals.py +0 -0
  196. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_optimize_evaluation.py +0 -0
  197. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_optimize_extractors.py +0 -0
  198. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_optimize_introspection.py +0 -0
  199. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_optimize_judges.py +0 -0
  200. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_optimize_metrics.py +0 -0
  201. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_optimize_proposers.py +0 -0
  202. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_optimize_retry.py +0 -0
  203. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_patches.py +0 -0
  204. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_policy.py +0 -0
  205. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_public_dataclass_compat.py +0 -0
  206. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_security.py +0 -0
  207. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_skills.py +0 -0
  208. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_structured_evals.py +0 -0
  209. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_taint_tracker.py +0 -0
  210. {adapt_agent-0.3.2 → adapt_agent-0.4.0}/tests/test_trust.py +0 -0
@@ -7,6 +7,205 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.4.0] - 2026-08-27
11
+
12
+ ### Added
13
+
14
+ - **`verbose=True` now reports where a long run actually is, not just what it
15
+ found.** Per-trial logging showed a candidate's score with no sense of
16
+ position or pace -- indistinguishable, over a run measured in hours, from a
17
+ hang. Every optimizer's trial log now includes the trial's position out of
18
+ the run's evaluation budget and the wall-clock time elapsed since the run
19
+ started (`trial 5/60 score=0.7100 ACCEPT (142.3s elapsed)`), and the
20
+ baseline-start line states the budget up front
21
+ (`baseline score=0.4100 (search budget: up to 60 evals)`).
22
+ `PipelineOptimizer` -- which overrides `optimize()` entirely and so never
23
+ ran the base class's verbose logging for itself, only ever forwarding
24
+ `verbose` into each stage -- now also logs its own stage transitions
25
+ (`stage 2/4 (coordinate_ascent) starting: 15/60 evals used so far (203.1s
26
+ elapsed)`) and a completion summary, so a multi-stage `make_default_optimizer`
27
+ pipeline no longer reads as one unexplained pause between each stage's own
28
+ output.
29
+
30
+ `OptimizationResult` gained `duration_seconds` (wall-clock time for the
31
+ whole `optimize` call), appended at the end of the dataclass per its
32
+ append-only convention, surfaced in `to_dict()`, `repr()`, and the
33
+ provenance header `to_config()` writes to a tuned-config file.
34
+
35
+ - **`OptimizableAgent` (and every `from_*` constructor, plus `wrap()`) gained
36
+ `exclude=` and `replace=`, so introspection catching up to a hand-bound
37
+ workaround no longer silently doubles a knob.** Parameters are opaque
38
+ getter/setter closures, so the library cannot detect that two of them target
39
+ the *same* underlying storage under *different* names — only that two names
40
+ collide, which already raised. The gap: a caller hand-binds a parameter to
41
+ work around introspection missing a knob (a real, documented, and until now
42
+ the *only* supported reason to declare extra parameters), a later release
43
+ teaches introspection to find that same knob under its own name, and the two
44
+ now coexist pointing at one piece of storage — doubling that part of the
45
+ search space and letting whichever knob an optimizer applies last silently
46
+ overwrite the other's candidate. Nothing raised, because the names never
47
+ matched.
48
+
49
+ `exclude={"agent.instructions", ...}` removes named introspected parameters
50
+ from the space before declared `parameters` are merged in — the fix for the
51
+ reported shape, where the hand-bound and introspected knobs have different
52
+ names. `replace=True` instead lets a declared parameter *under the same
53
+ name* as an introspected one win outright, for overriding how a knob with a
54
+ name you want to keep is read/written. The default collision behavior
55
+ (`ValueError`) is unchanged — an unintended same-name clash is still caught,
56
+ since `replace` is opt-in. New primitives underneath: `SearchSpace.remove(name)`
57
+ (drop a knob, never raises for a missing name) and `SearchSpace.add(parameter,
58
+ replace=True)`; `OptimizableAgent.add_parameter(parameter, replace=True)`
59
+ exposes the same override after construction.
60
+
61
+ ### Fixed
62
+
63
+ - **A completion callable that could not accept `system=` made the judge
64
+ grade silently without its rubric.** Every judge call
65
+ (`score`/`critique`/`compare`/`improve_prompt`) sends the grading
66
+ rubric/instructions via `complete(prompt, system=...)`. A callable
67
+ conforming to the *documented* `CompletionFn` shape — a plain
68
+ `def f(prompt: str) -> str`, exactly the form shown in the quick-start
69
+ example — raises `TypeError` on that call, and both places that catch it
70
+ (`CallableProvider.complete`, and `LLMJudge._invoke` for a bare callable
71
+ passed directly) fell back to `complete(prompt)`, dropping the rubric with
72
+ no warning. The judge kept returning normal-looking scores, indistinguishable
73
+ from a working judge, while silently grading blind on every call — a search
74
+ can burn its entire budget on an oracle that was never actually applying the
75
+ rubric. Both fallback sites now emit a `logger.warning` naming the dropped
76
+ keyword and how to fix the callable's signature; behavior is otherwise
77
+ unchanged; nothing raises. `CompletionFn`'s type widened from
78
+ `Callable[[str], str]` to `Callable[..., str]` to stop documenting a
79
+ signature that silently loses information in practice.
80
+
81
+ - **Optimizers stop paying for configurations they have already measured.**
82
+ Every optimizer now carries an evaluation cache (`cache_evaluations=True`)
83
+ keyed on the *live parameter state* at evaluation time, living for exactly
84
+ one `optimize` call. `PipelineOptimizer` injects one shared cache into its
85
+ stages -- and this is where the money was: each stage begins by evaluating
86
+ its baseline over the full dataset, and each stage's baseline *is* the
87
+ previous stage's winner, already measured. The default four-stage
88
+ `make_default_optimizer` pipeline was spending five full-dataset baseline
89
+ passes per run, four of them redundant; they are now cache hits. Keying on
90
+ the live state (not the candidate diff) is what makes those hits happen,
91
+ since a stage baseline and the winning trial that produced it reach the
92
+ same state through different configs.
93
+
94
+ The cost machinery this loop already had is deliberately untouched:
95
+ incomplete reports (rows lost to transient provider failures) are never
96
+ cached, so the baseline's re-run-on-incomplete path always re-measures, and
97
+ a cache hit still performs the restore/apply so proposers observe identical
98
+ live state either way. Live objects in the state (tool lists holding
99
+ callables) key by identity -- distinct-but-equal objects miss rather than
100
+ falsely hit, which only costs a re-run. Trial histories are unchanged:
101
+ cached trials record the same scores in the same order, so results are
102
+ byte-identical for a deterministic agent. Pass `cache_evaluations=False`
103
+ (per optimizer or per stage -- a stage's opt-out is respected inside a
104
+ caching pipeline) to re-measure every configuration, e.g. to average out a
105
+ stochastic agent.
106
+
107
+ - **Every supported framework is now drivable through one entry point --
108
+ including the three whose agents cannot run themselves.** An OpenAI Agents
109
+ SDK `Agent` is a configuration object executed by `Runner.run_sync(agent,
110
+ input)`; a Claude Agent SDK setup is a `ClaudeAgentOptions` driven by
111
+ `query(prompt=..., options=...)`; a bare Google ADK agent runs inside a
112
+ session-holding `Runner`. `resolve_runner` knows none of that, so
113
+ `evaluate_agent(...)`, `framework_runner(...)` and
114
+ `OptimizableAgent.from_agent(...)` -- the documented "point it at your agent"
115
+ entry points -- raised `TypeError` for exactly these frameworks, and every
116
+ example hand-wrote the driving lambda the SDK documents anyway.
117
+
118
+ Two new runner builders close the gap, mirroring `adk_runner`:
119
+
120
+ - **`openai_agents_runner(agent, *, runner=None, run_kwargs=None,
121
+ output_extractor=...)`** drives the agent through the SDK's `Runner`
122
+ (imported lazily; inject anything with a compatible `run_sync` -- or an
123
+ async `run`, which is awaited -- for tests and custom runners), forwarding
124
+ `run_kwargs` (e.g. `max_turns`, a shared `context`) to every call.
125
+ - **`claude_agent_runner(options, *, query_fn=None, output_extractor=...)`**
126
+ drives `query`, draining the async message stream synchronously and
127
+ extracting the final `ResultMessage` text. The options object is closed
128
+ over, not copied -- so the runner always reads the *live* object the
129
+ introspected parameters mutate, which is exactly what optimization needs.
130
+
131
+ `framework_runner` now **delegates by detected framework** when an object
132
+ exposes no run method at all (OpenAI Agents -> `openai_agents_runner`,
133
+ Claude options -> `claude_agent_runner`, bare ADK agent -> `adk_runner`),
134
+ and `OptimizableAgent.from_agent` falls back to `framework_runner` when
135
+ `resolve_runner` finds nothing runnable. Directly-runnable frameworks are
136
+ untouched -- resolution is attempted first and wins, so existing behaviour
137
+ (including framework-native outputs) is byte-identical; the fallback only
138
+ replaces a `TypeError`, never a working runner. A missing SDK surfaces as an
139
+ `ImportError` naming the extra to install, which is strictly more useful
140
+ than the `TypeError` it replaces. Both builders are exported from
141
+ `adapt_agent.optimization` and `adapt_agent.evaluation`.
142
+
143
+ - **Framework introspection reaches the knobs that steer multi-agent behaviour,
144
+ not just the ones on the front agent.** Two whole categories of tunable text
145
+ were invisible to the optimizer:
146
+
147
+ - *OpenAI Agents SDK*: an agent's `handoff_description` -- the text a routing
148
+ agent reads when deciding whether to delegate to it -- is now a PROMPT
149
+ parameter, and a `Handoff` **wrapper** in a `handoffs` list now contributes
150
+ its `tool_description` (as `<agent>_handoff.tool_description`). A wrapper
151
+ holds its agent inside an `on_invoke_handoff` closure, so the sub-agent
152
+ itself is structurally unreachable -- but the description is what the
153
+ routing LLM actually reads, and it is the part worth tuning. Previously a
154
+ topology built with `handoff(...)` lost the wrapped branch entirely, and
155
+ nothing tuned routing text anywhere.
156
+
157
+ - *Claude Agent SDK*: the subagent definitions in `options.agents` are now
158
+ introspected -- each definition's `prompt` and `description` (both
159
+ prompts: one steers the specialist, the other steers delegation *to* it),
160
+ its `model`, and its `tools`/`skills` lists (with the same drop-one
161
+ ablation candidates every other tool list gets), namespaced under the
162
+ slugged subagent name. Both real `AgentDefinition` objects and the plain
163
+ mappings the SDK also accepts are handled, and a subagent whose name
164
+ collides with the root component is skipped rather than allowed to produce
165
+ duplicate parameter names. The `agents` field was already recognised (it
166
+ stopped vetoing detection in 0.3.x) but its contents were never bound, so
167
+ a multi-agent Claude setup exposed only the orchestrator's knobs.
168
+
169
+ - **Sampling knobs the frameworks expose but introspection didn't.** Each is
170
+ bound with the same duck-typing rules as its neighbours (only when present,
171
+ only when the value has the right shape): CrewAI LLM objects gain `top_p`;
172
+ Google ADK `generate_content_config` gains `top_k` (bounded (1, 40), valid
173
+ for every Gemini generation so a gridded candidate never becomes a runtime
174
+ rejection); Pydantic AI `model_settings` and Microsoft Agent Framework
175
+ agents/clients/`default_options` gain `frequency_penalty` and
176
+ `presence_penalty` (bounded (-2.0, 2.0)); LangGraph bound chat models gain
177
+ `top_p`.
178
+
179
+ - **CrewAI `allow_delegation` is a searchable ROUTING parameter** with
180
+ `[True, False]` candidates -- whether an agent may hand work to its
181
+ crew-mates is a real topology decision the optimizer can now measure instead
182
+ of inherit. Bound only when the live value is a genuine `bool`.
183
+
184
+ - **Microsoft Agent Framework: the model is found even when the client hides
185
+ it.** When none of the client's model attributes exist, the per-agent
186
+ `model_id` override in `default_options` now binds as the MODEL parameter
187
+ (the client still wins when both are present). Previously such an agent had
188
+ no model knob at all despite the identifier sitting in its options mapping.
189
+
190
+ - **Claude Agent SDK: `max_thinking_tokens`** binds as a HYPERPARAM (bounds
191
+ (1024, 32000) -- the floor is the API's minimum thinking budget) on SDK
192
+ versions that carry it as a flat option.
193
+
194
+ ### Changed
195
+
196
+ - **OpenAI Agents SDK `model_settings.max_tokens` now carries bounds
197
+ (1, 32000)**, like every other framework's max-tokens knob. Boundless, its
198
+ search space collapsed to the current value -- `enumerate_candidates()`
199
+ returned one option and the numeric proposer skipped it -- so it appeared in
200
+ every report as a parameter the optimizer could never actually move.
201
+
202
+ - **CrewAI tasks with a `name` are namespaced under it** (slugged), falling
203
+ back to the positional `task_<index>` only when nameless. A name survives
204
+ reordering the task list; an index silently rebinds every exported tuned
205
+ config to a different task. A config exported before this release for a
206
+ *named* task addresses `task_<index>` and will no longer re-apply -- re-run
207
+ `to_config()` (unnamed tasks are unaffected).
208
+
10
209
  ## [0.3.2] - 2026-08-24
11
210
 
12
211
  ### Changed
@@ -67,6 +266,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
67
266
  report its version and each has the same fix. A diagnostic that fails on a
68
267
  damaged file tells you least exactly when you need it most.
69
268
 
269
+ ### Fixed
270
+
271
+ - **The evaluation cache could serve a report across two different
272
+ evaluators.** The cache key covered only the dataset identity and live
273
+ parameter state, so a `PipelineOptimizer` assembled from stages carrying
274
+ *different* harnesses (different metrics, a different judge) could let one
275
+ stage reuse a report another stage's harness produced, ranking candidates
276
+ against the wrong scores. The harness identity is now part of the key;
277
+ stages sharing one harness instance (how `make_default_optimizer` builds
278
+ pipelines) still share hits.
279
+ - **A pipeline-level `cache_evaluations=False` didn't reach its stages.**
280
+ `PipelineOptimizer(..., cache_evaluations=False)` cleared the *shared*
281
+ cache, but a stage left at its own default (`True`) then built a private
282
+ per-stage cache anyway, so repeated configurations were still served from
283
+ cache instead of re-measured. The pipeline's opt-out now suppresses stage
284
+ caching for the run; a stage's own opt-out inside a caching pipeline is
285
+ still respected.
286
+ - **Two CrewAI tasks with the same (or identically-slugged) `name` collided.**
287
+ 0.3.2's task-naming-by-`name` change (below) could make two tasks emit the
288
+ same component (`research_task.description` for both), and constructing the
289
+ `OptimizableAgent` raised `ValueError: Duplicate parameter name`. Colliding
290
+ names are now disambiguated with the task's positional index, so upgrading
291
+ to named-task components can no longer break agents whose tasks share a
292
+ name.
293
+ - **`PipelineOptimizer` assumed every stage carries a live `Optimizer`
294
+ state.** Cache injection read `stage._eval_cache` unconditionally, which
295
+ raised `AttributeError` for a minimal `Optimizer` subclass that
296
+ intentionally skips `Optimizer.__init__` (a pattern the cache's own read/
297
+ write helpers already tolerate via `getattr`). Stage attribute access is
298
+ now equally defensive.
299
+
70
300
 
71
301
  ## [0.3.1] - 2026-08-24
72
302
 
@@ -1861,7 +2091,9 @@ package that actually installs and is usable end to end.
1861
2091
  optimization, adversarial defense, evaluation, observability, patches, and the
1862
2092
  `adapt-agent` CLI (`info` command).
1863
2093
 
1864
- [Unreleased]: https://github.com/CodeHalwell/ADAPT-Agent/compare/v0.3.1...HEAD
2094
+ [Unreleased]: https://github.com/CodeHalwell/ADAPT-Agent/compare/v0.4.0...HEAD
2095
+ [0.4.0]: https://github.com/CodeHalwell/ADAPT-Agent/compare/v0.3.2...v0.4.0
2096
+ [0.3.2]: https://github.com/CodeHalwell/ADAPT-Agent/releases/tag/v0.3.2
1865
2097
  [0.3.1]: https://github.com/CodeHalwell/ADAPT-Agent/releases/tag/v0.3.1
1866
2098
  [0.3.0]: https://github.com/CodeHalwell/ADAPT-Agent/releases/tag/v0.3.0
1867
2099
  [0.2.0]: https://github.com/CodeHalwell/ADAPT-Agent/compare/v0.1.0...v0.2.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: adapt-agent
3
- Version: 0.3.2
3
+ Version: 0.4.0
4
4
  Summary: ADAPT-Agent: Adversarial Defense & Policy Training for LLM Agents - A comprehensive library for LLM agent optimization and security
5
5
  Author-email: Daniel Halwell <danielhalwell@gmail.com>
6
6
  Maintainer-email: Daniel Halwell <danielhalwell@gmail.com>
@@ -4,7 +4,7 @@ ADAPT-Agent: Adversarial Defense & Policy Training for LLM Agents
4
4
  A comprehensive library for LLM agent optimization and security.
5
5
  """
6
6
 
7
- __version__ = "0.3.2"
7
+ __version__ = "0.4.0"
8
8
 
9
9
  # Core exports
10
10
  # Framework adapters
@@ -40,7 +40,13 @@ from adapt_agent.optimization.metrics import (
40
40
  regex_match,
41
41
  token_f1,
42
42
  )
43
- from adapt_agent.optimization.runners import adk_runner, framework_runner, langgraph_inputs
43
+ from adapt_agent.optimization.runners import (
44
+ adk_runner,
45
+ claude_agent_runner,
46
+ framework_runner,
47
+ langgraph_inputs,
48
+ openai_agents_runner,
49
+ )
44
50
 
45
51
  logger = logging.getLogger(__name__)
46
52
 
@@ -209,4 +215,6 @@ __all__ = [
209
215
  "framework_runner",
210
216
  "langgraph_inputs",
211
217
  "adk_runner",
218
+ "openai_agents_runner",
219
+ "claude_agent_runner",
212
220
  ]
@@ -115,7 +115,13 @@ from adapt_agent.optimization.retry import (
115
115
  RetryPolicy,
116
116
  is_transient_error,
117
117
  )
118
- from adapt_agent.optimization.runners import adk_runner, framework_runner, langgraph_inputs
118
+ from adapt_agent.optimization.runners import (
119
+ adk_runner,
120
+ claude_agent_runner,
121
+ framework_runner,
122
+ langgraph_inputs,
123
+ openai_agents_runner,
124
+ )
119
125
  from adapt_agent.optimization.target import OptimizableAgent, wrap
120
126
 
121
127
  __all__ = [
@@ -175,6 +181,8 @@ __all__ = [
175
181
  "framework_runner",
176
182
  "langgraph_inputs",
177
183
  "adk_runner",
184
+ "openai_agents_runner",
185
+ "claude_agent_runner",
178
186
  # target
179
187
  "OptimizableAgent",
180
188
  "wrap",
@@ -15,6 +15,14 @@ string we bind it directly; when it is a mapping carrying an ``"append"`` key we
15
15
  bind that key so the appended instructions stay tunable while the preset itself
16
16
  is preserved.
17
17
 
18
+ Subagents are covered too. ``options.agents`` maps a subagent name to a
19
+ definition -- an ``AgentDefinition`` dataclass or an equivalent plain mapping --
20
+ whose ``prompt`` and ``description`` are the prompts that steer the subagent and
21
+ the delegation to it, alongside its own ``model``, ``tools`` and ``skills``.
22
+ Each definition is introspected under its (slugged) subagent name, so a
23
+ multi-agent Claude setup exposes every specialist's knobs, not just the
24
+ orchestrator's.
25
+
18
26
  Importing this module registers the introspector under the ``"claude_agent"``
19
27
  key.
20
28
  """
@@ -69,6 +77,81 @@ def _predicate(obj: Any) -> bool:
69
77
  return False
70
78
 
71
79
 
80
+ def _slug(text: Any) -> str | None:
81
+ """Slugify a subagent name (lowercase, spaces -> underscores). ``None`` if empty."""
82
+ if not isinstance(text, str):
83
+ return None
84
+ slug = text.strip().lower().replace(" ", "_")
85
+ return slug or None
86
+
87
+
88
+ def _definition_params(definition: Any, component: str) -> list[Parameter]:
89
+ """Bind one subagent definition's tunable fields.
90
+
91
+ A definition is an ``AgentDefinition`` dataclass in the real SDK, but the
92
+ SDK also accepts plain mappings, so both shapes are handled: attributes for
93
+ objects, keys for mappings. ``prompt`` and ``description`` are both PROMPT
94
+ knobs -- the prompt steers what the subagent does, the description steers
95
+ when the orchestrator delegates to it -- and ``tools``/``skills`` get the
96
+ same drop-one ablation candidates as every other tool list so subagent tool
97
+ selection is a real search space.
98
+ """
99
+ is_mapping = isinstance(definition, dict)
100
+
101
+ def _value(field: str) -> Any:
102
+ return definition.get(field) if is_mapping else getattr(definition, field, None)
103
+
104
+ def _bind(
105
+ field: str, name: str, kind: ParameterKind, candidates: list[Any] | None = None
106
+ ) -> Parameter | None:
107
+ if is_mapping:
108
+ return bind_item(
109
+ definition, field, name, kind, component=component, candidates=candidates
110
+ )
111
+ return bind_attr(definition, field, name, kind, component=component, candidates=candidates)
112
+
113
+ params: list[Parameter] = []
114
+ for field in ("prompt", "description"):
115
+ if isinstance(_value(field), str):
116
+ param = _bind(field, f"{component}.{field}", ParameterKind.PROMPT)
117
+ if param is not None:
118
+ params.append(param)
119
+ if isinstance(_value("model"), str):
120
+ param = _bind("model", f"{component}.model", ParameterKind.MODEL)
121
+ if param is not None:
122
+ params.append(param)
123
+ for field, kind in (("tools", ParameterKind.TOOL), ("skills", ParameterKind.SKILL)):
124
+ current = _value(field)
125
+ if isinstance(current, (list, tuple)):
126
+ param = _bind(
127
+ field, f"{component}.{field}", kind, tool_subset_candidates(current) or None
128
+ )
129
+ if param is not None:
130
+ params.append(param)
131
+ return params
132
+
133
+
134
+ def _introspect_subagents(obj: Any, params: list[Parameter]) -> None:
135
+ """Introspect the subagent definitions in ``options.agents``.
136
+
137
+ Each definition is namespaced under its slugged dict key. Names already
138
+ emitted are skipped rather than duplicated -- a subagent literally named
139
+ ``"agent"`` would otherwise collide with the root component's own knobs and
140
+ make ``SearchSpace.add`` raise downstream.
141
+ """
142
+ definitions = getattr(obj, "agents", None)
143
+ if not isinstance(definitions, dict):
144
+ return
145
+ seen = {p.name for p in params}
146
+ for index, (key, definition) in enumerate(definitions.items()):
147
+ component = _slug(key) or f"subagent_{index}"
148
+ for param in _definition_params(definition, component):
149
+ if param.name in seen:
150
+ continue
151
+ seen.add(param.name)
152
+ params.append(param)
153
+
154
+
72
155
  def _introspect_system_prompt(obj: Any, component: str) -> list[Parameter]:
73
156
  """Bind the ``system_prompt`` (string, or preset mapping with ``"append"``)."""
74
157
  prompt = getattr(obj, "system_prompt", None)
@@ -137,6 +220,16 @@ def _introspect(obj: Any) -> list[Parameter]:
137
220
  component=component,
138
221
  bounds=(1, 100),
139
222
  ),
223
+ # The extended-thinking budget (present on SDK versions that carry
224
+ # it as a flat option). The floor matches the API's minimum budget.
225
+ bind_attr(
226
+ obj,
227
+ "max_thinking_tokens",
228
+ f"{component}.max_thinking_tokens",
229
+ ParameterKind.HYPERPARAM,
230
+ component=component,
231
+ bounds=(1024, 32000),
232
+ ),
140
233
  # ``permission_mode`` is a small discrete enum; giving it explicit
141
234
  # candidates turns it from a dead knob into a searchable one.
142
235
  bind_attr(
@@ -149,6 +242,8 @@ def _introspect(obj: Any) -> list[Parameter]:
149
242
  ),
150
243
  ]
151
244
  params.extend(p for p in candidates if p is not None)
245
+
246
+ _introspect_subagents(obj, params)
152
247
  except Exception:
153
248
  return params
154
249
  return params
@@ -8,9 +8,12 @@ importing ``crewai``: everything is discovered by duck-typing with ``getattr``.
8
8
 
9
9
  For each agent we expose its ``role``/``goal``/``backstory`` prompts, its model
10
10
  (a string identifier, or an introspected LLM object's
11
- ``model``/``temperature``/``max_tokens``), its ``tools`` allow-list, and its
12
- ``max_iter`` hyperparameter. For each task we expose its ``description`` and
13
- ``expected_output`` prompts.
11
+ ``model``/``temperature``/``top_p``/``max_tokens``), its ``tools`` allow-list,
12
+ its ``max_iter`` hyperparameter, and its ``allow_delegation`` routing switch
13
+ (with ``[True, False]`` candidates, so the optimizer can search whether letting
14
+ the agent delegate helps or hurts). For each task we expose its ``description``
15
+ and ``expected_output`` prompts, named after the task's ``name`` when it has one
16
+ (falling back to the positional ``task_<index>``).
14
17
 
15
18
  Importing this module registers the introspector under the ``"crewai"`` key.
16
19
  """
@@ -76,6 +79,14 @@ def _introspect_llm(llm: Any, component: str) -> list[Parameter]:
76
79
  component=component,
77
80
  bounds=(0.0, 2.0),
78
81
  ),
82
+ bind_attr(
83
+ llm,
84
+ "top_p",
85
+ f"{component}.top_p",
86
+ ParameterKind.HYPERPARAM,
87
+ component=component,
88
+ bounds=(0.0, 1.0),
89
+ ),
79
90
  bind_attr(
80
91
  llm,
81
92
  "max_tokens",
@@ -144,12 +155,55 @@ def _introspect_agent(agent: Any, index: int) -> list[Parameter]:
144
155
  if max_iter is not None:
145
156
  params.append(max_iter)
146
157
 
158
+ # ``allow_delegation`` decides whether this agent may hand work to its
159
+ # crew-mates -- a routing switch, and with explicit candidates a genuinely
160
+ # searchable one: the optimizer can measure whether delegation helps or
161
+ # just adds hops. Bound only when it is a real bool, so a Mock-ish or
162
+ # unset attribute never turns into a knob.
163
+ if isinstance(getattr(agent, "allow_delegation", None), bool):
164
+ delegation = bind_attr(
165
+ agent,
166
+ "allow_delegation",
167
+ f"{component}.allow_delegation",
168
+ ParameterKind.ROUTING,
169
+ component=component,
170
+ candidates=[True, False],
171
+ )
172
+ if delegation is not None:
173
+ params.append(delegation)
174
+
147
175
  return params
148
176
 
149
177
 
150
- def _introspect_task(task: Any, index: int) -> list[Parameter]:
151
- """Introspect a single CrewAI ``Task``."""
152
- component = f"task_{index}"
178
+ def _unique_component(component: str, index: int, used: set[str]) -> str:
179
+ """Return ``component``, suffixed with the positional index while taken.
180
+
181
+ Two tasks may share a name (or names that slug identically), and a name
182
+ can even slug to another task's positional fallback. Identical components
183
+ would emit identical parameter names -- a duplicate ``SearchSpace`` entry
184
+ downstream -- where the positional components could never collide. The
185
+ index is unique per task, so one suffix round settles ordinary collisions;
186
+ the loop covers a name crafted to match the suffixed form as well.
187
+ """
188
+ candidate = component
189
+ while candidate in used:
190
+ candidate = f"{candidate}_{index}"
191
+ used.add(candidate)
192
+ return candidate
193
+
194
+
195
+ def _introspect_task(task: Any, index: int, used: set[str]) -> list[Parameter]:
196
+ """Introspect a single CrewAI ``Task``.
197
+
198
+ A task with a ``name`` is namespaced under it (slugged): the name survives
199
+ reordering the task list, where a positional ``task_<index>`` silently
200
+ rebinds every exported config to a different task. Nameless tasks keep the
201
+ positional fallback, and a component already claimed by an earlier task is
202
+ disambiguated with the positional index (see :func:`_unique_component`).
203
+ """
204
+ component = _unique_component(
205
+ _slug(getattr(task, "name", None)) or f"task_{index}", index, used
206
+ )
153
207
  candidates = [
154
208
  bind_attr(
155
209
  task,
@@ -176,9 +230,10 @@ def _introspect(obj: Any) -> list[Parameter]:
176
230
  agents = getattr(obj, "agents", None) or []
177
231
  for i, agent in enumerate(agents):
178
232
  params.extend(_introspect_agent(agent, i))
233
+ used_task_components: set[str] = set()
179
234
  tasks = getattr(obj, "tasks", None) or []
180
235
  for i, task in enumerate(tasks):
181
- params.extend(_introspect_task(task, i))
236
+ params.extend(_introspect_task(task, i, used_task_components))
182
237
  except Exception:
183
238
  return params
184
239
  return params
@@ -13,9 +13,9 @@ importing ``google.adk``: everything is discovered by duck-typing with
13
13
  For each agent we expose its ``instruction`` and ``global_instruction`` prompts
14
14
  (only when they are plain strings), its model (a string identifier, or an
15
15
  introspected model object's ``model``/``model_name``), the
16
- ``temperature``/``top_p``/``max_output_tokens`` hyperparameters from its
17
- ``generate_content_config``, its ``tools`` allow-list, and its ``sub_agents``
18
- routing list. Nested ``sub_agents`` are introspected recursively and namespaced
16
+ ``temperature``/``top_p``/``top_k``/``max_output_tokens`` hyperparameters from
17
+ its ``generate_content_config``, its ``tools`` allow-list, and its
18
+ ``sub_agents`` routing list. Nested ``sub_agents`` are introspected recursively and namespaced
19
19
  under their own component names; an ``id()`` visited set guards against cycles.
20
20
 
21
21
  Importing this module registers the introspector under the ``"google_adk"`` key.
@@ -94,6 +94,16 @@ def _introspect_generate_config(config: Any, component: str) -> list[Parameter]:
94
94
  component=component,
95
95
  bounds=(0.0, 1.0),
96
96
  ),
97
+ # Kept within (1, 40): valid for every Gemini generation, so a gridded
98
+ # candidate never turns into a runtime rejection mid-search.
99
+ bind_attr(
100
+ config,
101
+ "top_k",
102
+ f"{component}.top_k",
103
+ ParameterKind.HYPERPARAM,
104
+ component=component,
105
+ bounds=(1, 40),
106
+ ),
97
107
  bind_attr(
98
108
  config,
99
109
  "max_output_tokens",
@@ -186,6 +186,16 @@ def _model_params(runnable: Any, component: str) -> list[Parameter]:
186
186
  bounds=(0.0, 2.0),
187
187
  )
188
188
  )
189
+ params.append(
190
+ bind_attr(
191
+ model,
192
+ "top_p",
193
+ f"{component}.top_p",
194
+ ParameterKind.HYPERPARAM,
195
+ component=component,
196
+ bounds=(0.0, 1.0),
197
+ )
198
+ )
189
199
  params.append(
190
200
  bind_attr(
191
201
  model,
@@ -118,6 +118,8 @@ _HYPERPARAMS: tuple[tuple[str, tuple[float, float]], ...] = (
118
118
  ("temperature", (0.0, 2.0)),
119
119
  ("top_p", (0.0, 1.0)),
120
120
  ("max_tokens", (1, 32000)),
121
+ ("frequency_penalty", (-2.0, 2.0)),
122
+ ("presence_penalty", (-2.0, 2.0)),
121
123
  )
122
124
 
123
125
 
@@ -220,6 +222,7 @@ def _introspect(obj: Any) -> list[Parameter]:
220
222
  params.append(instructions)
221
223
 
222
224
  client = _client_of(obj)
225
+ found_model = False
223
226
  if client is not None:
224
227
  for attr in _MODEL_ATTRS:
225
228
  model = bind_attr(
@@ -231,9 +234,26 @@ def _introspect(obj: Any) -> list[Parameter]:
231
234
  )
232
235
  if model is not None:
233
236
  params.append(model)
237
+ found_model = True
234
238
  break
235
239
  params.extend(_introspect_hyperparams(client, component))
236
240
 
241
+ # A per-agent model override can live in ``default_options`` ("model_id"
242
+ # is a ChatOptions key) rather than on the client -- for a client that
243
+ # exposes no model attribute at all, this is the only place the model
244
+ # is tunable from.
245
+ if not found_model:
246
+ model = bind_mapping_key(
247
+ obj,
248
+ _OPTIONS_ATTR,
249
+ "model_id",
250
+ f"{component}.model",
251
+ ParameterKind.MODEL,
252
+ component=component,
253
+ )
254
+ if model is not None:
255
+ params.append(model)
256
+
237
257
  # Sampling settings may live on the agent itself, or in its options
238
258
  # mapping, rather than on the client. First source found wins per knob.
239
259
  seen = {p.name for p in params}