adapt-agent 0.3.1__tar.gz → 0.3.2__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.1 → adapt_agent-0.3.2}/CHANGELOG.md +61 -0
  2. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/PKG-INFO +1 -1
  3. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/__init__.py +1 -1
  4. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/cli/__init__.py +76 -1
  5. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/skills/__init__.py +235 -0
  6. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent.egg-info/PKG-INFO +1 -1
  7. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/docs/releasing.md +38 -17
  8. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/docs/skill.md +39 -0
  9. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_cli_skills.py +121 -0
  10. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_skills.py +169 -1
  11. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/CODE_OF_CONDUCT.md +0 -0
  12. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/CONTRIBUTING.md +0 -0
  13. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/LICENSE +0 -0
  14. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/MANIFEST.in +0 -0
  15. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/README.md +0 -0
  16. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/SECURITY.md +0 -0
  17. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/__main__.py +0 -0
  18. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/adapters/__init__.py +0 -0
  19. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/adapters/_governed.py +0 -0
  20. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/adapters/base.py +0 -0
  21. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/adapters/claude_agent/__init__.py +0 -0
  22. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/adapters/crewai/__init__.py +0 -0
  23. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/adapters/google_adk/__init__.py +0 -0
  24. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/adapters/langgraph/__init__.py +0 -0
  25. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/adapters/microsoft_agent_framework/__init__.py +0 -0
  26. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/adapters/openai_agents/__init__.py +0 -0
  27. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/adapters/pydantic_ai/__init__.py +0 -0
  28. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/adversarial/__init__.py +0 -0
  29. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/cli/__main__.py +0 -0
  30. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/core/__init__.py +0 -0
  31. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/core/governance.py +0 -0
  32. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/core/memory.py +0 -0
  33. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/core/middleware.py +0 -0
  34. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/core/policy.py +0 -0
  35. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/core/trust.py +0 -0
  36. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/core/types.py +0 -0
  37. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/evaluation/__init__.py +0 -0
  38. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/exceptions.py +0 -0
  39. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/integrations/__init__.py +0 -0
  40. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/integrations/_common.py +0 -0
  41. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/integrations/agent_framework.py +0 -0
  42. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/integrations/claude_agent.py +0 -0
  43. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/integrations/crewai.py +0 -0
  44. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/integrations/google_adk.py +0 -0
  45. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/integrations/langgraph.py +0 -0
  46. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/integrations/openai_agents.py +0 -0
  47. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/integrations/pydantic_ai.py +0 -0
  48. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/observability/__init__.py +0 -0
  49. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/__init__.py +0 -0
  50. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/config.py +0 -0
  51. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/dataset.py +0 -0
  52. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/evals.py +0 -0
  53. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/evaluation.py +0 -0
  54. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/extractors.py +0 -0
  55. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/introspection/__init__.py +0 -0
  56. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/introspection/claude_agent.py +0 -0
  57. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/introspection/crewai.py +0 -0
  58. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/introspection/google_adk.py +0 -0
  59. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/introspection/langgraph.py +0 -0
  60. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/introspection/microsoft_agent_framework.py +0 -0
  61. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/introspection/openai_agents.py +0 -0
  62. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/introspection/pydantic_ai.py +0 -0
  63. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/judge.py +0 -0
  64. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/judges.py +0 -0
  65. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/metrics.py +0 -0
  66. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/optimizers.py +0 -0
  67. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/parameters.py +0 -0
  68. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/performance.py +0 -0
  69. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/proposers.py +0 -0
  70. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/providers.py +0 -0
  71. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/retry.py +0 -0
  72. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/runners.py +0 -0
  73. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/target.py +0 -0
  74. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/patches/__init__.py +0 -0
  75. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/py.typed +0 -0
  76. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/security/__init__.py +0 -0
  77. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/security/firewall.py +0 -0
  78. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/security/taint_tracker.py +0 -0
  79. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/skills/adapt-agent/SKILL.md +0 -0
  80. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/skills/adapt-agent/references/evals.md +0 -0
  81. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/skills/adapt-agent/references/guardrails.md +0 -0
  82. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/skills/adapt-agent/references/optimization.md +0 -0
  83. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent.egg-info/SOURCES.txt +0 -0
  84. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent.egg-info/dependency_links.txt +0 -0
  85. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent.egg-info/entry_points.txt +0 -0
  86. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent.egg-info/requires.txt +0 -0
  87. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent.egg-info/top_level.txt +0 -0
  88. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/docs/adapters.md +0 -0
  89. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/docs/api.md +0 -0
  90. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/docs/cli.md +0 -0
  91. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/docs/evals.md +0 -0
  92. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/docs/frameworks/claude_agent.md +0 -0
  93. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/docs/frameworks/crewai.md +0 -0
  94. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/docs/frameworks/google_adk.md +0 -0
  95. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/docs/frameworks/index.md +0 -0
  96. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/docs/frameworks/langgraph.md +0 -0
  97. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/docs/frameworks/microsoft_agent_framework.md +0 -0
  98. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/docs/frameworks/openai_agents.md +0 -0
  99. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/docs/frameworks/pydantic_ai.md +0 -0
  100. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/docs/index.md +0 -0
  101. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/docs/integrations.md +0 -0
  102. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/docs/observability.md +0 -0
  103. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/docs/optimization.md +0 -0
  104. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/docs/policy.md +0 -0
  105. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/docs/quickstart.md +0 -0
  106. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/docs/security.md +0 -0
  107. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/01_firewall_and_policy.py +0 -0
  108. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/02_adversarial_defense.py +0 -0
  109. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/03_langgraph_guarded_agent.py +0 -0
  110. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/04_observability.py +0 -0
  111. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/05_multi_framework_adapters.py +0 -0
  112. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/06_optimize_with_golden_dataset.py +0 -0
  113. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/07_train_from_yaml.py +0 -0
  114. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/08_agent_evals.py +0 -0
  115. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/README.md +0 -0
  116. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/claude_agent/01_basic_guarded.py +0 -0
  117. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/claude_agent/02_policy_observability_trust.py +0 -0
  118. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/claude_agent/03_evaluate_and_optimize.py +0 -0
  119. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/claude_agent/04_multi_agent_and_training.py +0 -0
  120. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/claude_agent/README.md +0 -0
  121. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/claude_agent/claude_agent.train.yaml +0 -0
  122. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/config.example.json +0 -0
  123. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/crewai/01_basic_guarded.py +0 -0
  124. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/crewai/02_policy_observability_trust.py +0 -0
  125. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/crewai/03_evaluate_and_optimize.py +0 -0
  126. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/crewai/04_multi_agent_and_training.py +0 -0
  127. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/crewai/README.md +0 -0
  128. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/crewai/crewai.train.yaml +0 -0
  129. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/google_adk/01_basic_guarded.py +0 -0
  130. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/google_adk/02_policy_observability_trust.py +0 -0
  131. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/google_adk/03_evaluate_and_optimize.py +0 -0
  132. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/google_adk/04_multi_agent_and_training.py +0 -0
  133. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/google_adk/README.md +0 -0
  134. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/google_adk/google_adk.train.yaml +0 -0
  135. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/langgraph/01_basic_guarded.py +0 -0
  136. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/langgraph/02_policy_observability_trust.py +0 -0
  137. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/langgraph/03_evaluate_and_optimize.py +0 -0
  138. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/langgraph/04_multi_agent_and_training.py +0 -0
  139. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/langgraph/README.md +0 -0
  140. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/langgraph/langgraph.train.yaml +0 -0
  141. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/microsoft_agent_framework/01_basic_guarded.py +0 -0
  142. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/microsoft_agent_framework/02_policy_observability_trust.py +0 -0
  143. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/microsoft_agent_framework/03_evaluate_and_optimize.py +0 -0
  144. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/microsoft_agent_framework/04_magentic_team_and_training.py +0 -0
  145. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/microsoft_agent_framework/README.md +0 -0
  146. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/microsoft_agent_framework/magentic.train.yaml +0 -0
  147. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/openai_agents/01_basic_guarded.py +0 -0
  148. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/openai_agents/02_policy_observability_trust.py +0 -0
  149. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/openai_agents/03_evaluate_and_optimize.py +0 -0
  150. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/openai_agents/04_multi_agent_and_training.py +0 -0
  151. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/openai_agents/README.md +0 -0
  152. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/openai_agents/openai_agents.train.yaml +0 -0
  153. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/pydantic_ai/01_basic_guarded.py +0 -0
  154. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/pydantic_ai/02_policy_observability_trust.py +0 -0
  155. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/pydantic_ai/03_evaluate_and_optimize.py +0 -0
  156. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/pydantic_ai/04_multi_agent_and_training.py +0 -0
  157. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/pydantic_ai/README.md +0 -0
  158. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/pydantic_ai/pydantic_ai.train.yaml +0 -0
  159. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/train.example.yaml +0 -0
  160. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/pyproject.toml +0 -0
  161. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/setup.cfg +0 -0
  162. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/__init__.py +0 -0
  163. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_action_refs.py +0 -0
  164. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_adapters.py +0 -0
  165. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_adversarial.py +0 -0
  166. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_adversarial_extra.py +0 -0
  167. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_async_governance.py +0 -0
  168. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_cli.py +0 -0
  169. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_cli_optimize.py +0 -0
  170. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_core.py +0 -0
  171. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_evaluation.py +0 -0
  172. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_exceptions.py +0 -0
  173. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_firewall_extra.py +0 -0
  174. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_governed_adapters.py +0 -0
  175. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_integrations.py +0 -0
  176. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_memory.py +0 -0
  177. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_middleware.py +0 -0
  178. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_observability.py +0 -0
  179. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_observability_extra.py +0 -0
  180. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimization.py +0 -0
  181. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_config.py +0 -0
  182. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_dataset.py +0 -0
  183. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_evals.py +0 -0
  184. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_evaluation.py +0 -0
  185. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_extractors.py +0 -0
  186. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_introspect_claude_agent.py +0 -0
  187. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_introspect_crewai.py +0 -0
  188. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_introspect_google_adk.py +0 -0
  189. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_introspect_langgraph.py +0 -0
  190. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_introspect_microsoft_agent_framework.py +0 -0
  191. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_introspect_openai_agents.py +0 -0
  192. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_introspect_pydantic_ai.py +0 -0
  193. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_introspection.py +0 -0
  194. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_judge.py +0 -0
  195. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_judges.py +0 -0
  196. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_metrics.py +0 -0
  197. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_optimizers.py +0 -0
  198. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_parameters.py +0 -0
  199. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_proposers.py +0 -0
  200. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_providers.py +0 -0
  201. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_retry.py +0 -0
  202. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_runners.py +0 -0
  203. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_target.py +0 -0
  204. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_patches.py +0 -0
  205. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_policy.py +0 -0
  206. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_public_dataclass_compat.py +0 -0
  207. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_security.py +0 -0
  208. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_structured_evals.py +0 -0
  209. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_taint_tracker.py +0 -0
  210. {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_trust.py +0 -0
@@ -7,6 +7,67 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.2] - 2026-08-24
11
+
12
+ ### Changed
13
+
14
+ - **Releasing is now a version bump plus a merge, not a tag push.**
15
+ `release.yml` also runs on `main`, asks PyPI whether `__version__` is
16
+ published yet, and releases it when it is not — creating the `vX.Y.Z` tag
17
+ itself, *after* the upload, so a tag always means "this is on PyPI". Pushing
18
+ a tag by hand still works and takes the same path.
19
+
20
+ Whether to release is decided by querying the index the upload would go to,
21
+ not by diffing against the previous commit: that answer stays correct for a
22
+ revert, a re-run, a merge commit, and a branch that lands out of order. When
23
+ PyPI cannot be reached the answer is *no release* — a missed release is
24
+ recoverable, and a PyPI filename can never be reused.
25
+
26
+ **Automatic does not mean unattended**: the `pypi` environment's required
27
+ reviewer still gates the upload. What goes away is the tag ceremony, not the
28
+ human — which is also the backstop against a stray version bump publishing
29
+ itself.
30
+
31
+ Two conditions had to change rather than be extended, and both would have
32
+ misfired the moment `main` joined the triggers: `publish-pypi` keyed off
33
+ `github.event_name == 'push'`, which is true for a *branch* push and would
34
+ have attempted an upload on every commit; and `github-release` used
35
+ `github.ref_name` as the tag, which on a `main` push is the string `main`.
36
+ An ordinary commit now costs one short job that finds the version already
37
+ published and skips everything downstream.
38
+
39
+ ### Added
40
+
41
+ - **`adapt skills --check`, because an installed skill could not say which
42
+ version it was.** Installing copies files into `.claude/skills/`, and
43
+ upgrading the library does not move that copy — documented, but silent when
44
+ forgotten, and nothing in the directory recorded where it came from. A 0.2.0
45
+ copy therefore sat in a real project under a newer library, feeding an agent
46
+ guidance for a release two behind including two behaviours that had since
47
+ been fixed, and the only way it surfaced was a hand diff against the wheel.
48
+
49
+ Each install now writes a `.adapt-skill.json` manifest recording the library
50
+ version, the timestamp, and a SHA-256 per file — derived from what was
51
+ actually written, never a second hand-kept list. `adapt skills --check`
52
+ compares it to the running library and **exits non-zero**, so the check
53
+ belongs in CI beside the lint step rather than in someone's memory.
54
+
55
+ Four states, because they need different fixes: up to date, *stale* (an older
56
+ release), *version unknown* (no manifest — installed before this existed, or
57
+ copied by hand), and *locally modified*. The third is the one worth naming:
58
+ reading "cannot say" as "up to date" would hide exactly the case this exists
59
+ to surface. A local edit is reported but does **not** fail the check — editing
60
+ your own copy is supported, and failing a build over it would only teach
61
+ people to stop running the check.
62
+
63
+ New API alongside the CLI: `skill_status()`, `installed_skill()`,
64
+ `InstalledSkill`, `SkillStatus`, `MANIFEST_FILE`, and `InstallResult.version`.
65
+ `installed_skill()` never raises — absent, unreadable, malformed and
66
+ wrong-shaped manifests are one answer, since each means the install cannot
67
+ report its version and each has the same fix. A diagnostic that fails on a
68
+ damaged file tells you least exactly when you need it most.
69
+
70
+
10
71
  ## [0.3.1] - 2026-08-24
11
72
 
12
73
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: adapt-agent
3
- Version: 0.3.1
3
+ Version: 0.3.2
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.1"
7
+ __version__ = "0.3.2"
8
8
 
9
9
  # Core exports
10
10
  # Framework adapters
@@ -192,6 +192,23 @@ def main(args: list[str] | None = None) -> int:
192
192
  )
193
193
 
194
194
  skills_parser = subparsers.add_parser("skills", help="List the bundled agent skills")
195
+ skills_parser.add_argument(
196
+ "--check",
197
+ action="store_true",
198
+ help="Compare the installed skills against this library version and "
199
+ "exit non-zero if any is missing, stale, or of unknown version.",
200
+ )
201
+ skills_parser.add_argument(
202
+ "--target",
203
+ choices=_SKILL_TARGETS,
204
+ default="project",
205
+ help="Which installation --check inspects (default: project).",
206
+ )
207
+ skills_parser.add_argument(
208
+ "--dir",
209
+ dest="directory",
210
+ help="Explicit skills directory for --check, overriding --target.",
211
+ )
195
212
  skills_parser.add_argument(
196
213
  "--json", action="store_true", help="Emit machine-readable JSON output"
197
214
  )
@@ -487,8 +504,9 @@ def _cmd_install(args: Any) -> int:
487
504
 
488
505
  for result in results:
489
506
  verb = "Updated" if result.replaced else "Installed"
507
+ version = f" v{result.version}" if result.version else ""
490
508
  print(
491
- f"{verb} the '{result.skill.name}' skill "
509
+ f"{verb} the '{result.skill.name}' skill{version} "
492
510
  f"({len(result.files)} files) -> {_display_path(result.path)}"
493
511
  )
494
512
  where = "this project" if args.target == "project" and not args.directory else "you"
@@ -505,6 +523,9 @@ def _cmd_skills(args: Any) -> int:
505
523
  except Exception as exc:
506
524
  return _fail(exc, args.json)
507
525
 
526
+ if getattr(args, "check", False):
527
+ return _cmd_skills_check(args, skills)
528
+
508
529
  if args.json:
509
530
  print(json.dumps({"skills": [s.to_dict() for s in skills]}, indent=2))
510
531
  return 0
@@ -523,6 +544,60 @@ def _cmd_skills(args: Any) -> int:
523
544
  return 0
524
545
 
525
546
 
547
+ def _cmd_skills_check(args: Any, skills: list[Any]) -> int:
548
+ """Report installed-vs-running for each bundled skill; non-zero if any is not current.
549
+
550
+ An installed skill does not follow the library when it is upgraded, and
551
+ until it carried a manifest nothing in the directory said which version it
552
+ was -- so a stale copy could feed an agent guidance from an older release,
553
+ including behaviour that had since been fixed, and only a hand diff against
554
+ the wheel would show it. The exit code is the point: this belongs in CI
555
+ next to the lint step, not in someone's memory.
556
+ """
557
+ from adapt_agent.skills import skill_status
558
+
559
+ try:
560
+ statuses = [skill_status(skill, args.directory, target=args.target) for skill in skills]
561
+ except Exception as exc:
562
+ return _fail(exc, args.json)
563
+
564
+ # Locally modified is *not* a failure: someone editing their own copy on
565
+ # purpose is a supported thing to do, and failing a build over it would
566
+ # teach people to stop running the check. Reported, not enforced.
567
+ outdated = [s for s in statuses if not s.present or s.unknown or s.stale]
568
+
569
+ if args.json:
570
+ print(
571
+ json.dumps(
572
+ {
573
+ "status": "ok" if not outdated else "outdated",
574
+ "skills": [s.to_dict() for s in statuses],
575
+ },
576
+ indent=2,
577
+ )
578
+ )
579
+ return 1 if outdated else 0
580
+
581
+ if not statuses:
582
+ print("No bundled skills to check.")
583
+ return 0
584
+
585
+ for status in statuses:
586
+ print(f" {status.summary()}")
587
+ if outdated:
588
+ names = " ".join(sorted({s.name for s in outdated}))
589
+ # `--force` only when something is there to replace: telling someone to
590
+ # force an install that has no existing directory reads as though the
591
+ # command were dangerous when it is the ordinary first install.
592
+ force = " --force" if any(s.present for s in outdated) else ""
593
+ print()
594
+ print(f"Install with: {_prog_name()} install skill {names}{force}")
595
+ return 1
596
+ print()
597
+ print("Installed skills match this library version.")
598
+ return 0
599
+
600
+
526
601
  def _first_sentence(text: str, limit: int = 160) -> str:
527
602
  """First sentence of a description, truncated for a one-line listing."""
528
603
  sentence = text.strip().split(". ")[0].rstrip(".")
@@ -31,12 +31,15 @@ imports an agent framework or an LLM SDK.
31
31
 
32
32
  from __future__ import annotations
33
33
 
34
+ import hashlib
35
+ import json
34
36
  import re
35
37
  import shutil
36
38
  import sys
37
39
  import tempfile
38
40
  from collections.abc import Iterator
39
41
  from dataclasses import dataclass
42
+ from datetime import datetime, timezone
40
43
  from importlib import resources
41
44
  from pathlib import Path
42
45
  from typing import TYPE_CHECKING, Any
@@ -56,6 +59,14 @@ from adapt_agent.exceptions import SkillError
56
59
  #: The entrypoint file every skill directory must contain.
57
60
  SKILL_FILE = "SKILL.md"
58
61
 
62
+ #: Written into an installed skill directory to record where it came from.
63
+ #: Dotted so it sorts out of the way and no agent scanning for ``SKILL.md``
64
+ #: mistakes it for content. Absent from an install made before this existed,
65
+ #: and from a hand-copied directory -- both of which read as "unknown" rather
66
+ #: than as up to date, because an install that cannot say what it is is
67
+ #: exactly the case this file exists to stop being invisible.
68
+ MANIFEST_FILE = ".adapt-skill.json"
69
+
59
70
  #: Where each install target places skill directories, relative to its root.
60
71
  #: ``project`` is resolved against the working directory (or an explicit root),
61
72
  #: ``user`` against the user's home directory. Both are the directories Claude
@@ -148,12 +159,97 @@ class InstallResult:
148
159
  #: ``True`` when an existing installation at :attr:`path` was replaced.
149
160
  replaced: bool = False
150
161
 
162
+ #: The library version that produced this install, recorded in its manifest.
163
+ version: str = ""
164
+
151
165
  def to_dict(self) -> dict[str, Any]:
152
166
  return {
153
167
  "name": self.skill.name,
154
168
  "path": str(self.path),
155
169
  "files": list(self.files),
156
170
  "replaced": self.replaced,
171
+ "version": self.version,
172
+ }
173
+
174
+
175
+ @dataclass
176
+ class InstalledSkill:
177
+ """What an installed skill directory says about itself.
178
+
179
+ Read back from :data:`MANIFEST_FILE`. ``None`` from :func:`installed_skill`
180
+ means there is no manifest to read, which is not the same as "not
181
+ installed": a directory copied by hand, or installed by a version that
182
+ predates manifests, has files but cannot say which version they are.
183
+ """
184
+
185
+ name: str
186
+ version: str
187
+ installed_at: str
188
+ path: Path
189
+ #: Relative path -> SHA-256 of the bytes written at install time.
190
+ digests: dict[str, str]
191
+
192
+
193
+ @dataclass
194
+ class SkillStatus:
195
+ """How an installed skill compares to the running library.
196
+
197
+ The distinction that matters is between the three unhappy answers, because
198
+ they need different fixes: *missing* (install it), *unknown* (no manifest --
199
+ reinstall so it can be tracked), and *stale* (reinstall to pick up the
200
+ current guidance). ``modified`` is orthogonal to all three: it lists files
201
+ that differ from what was installed, which is the case where ``--force``
202
+ would discard someone's edits.
203
+ """
204
+
205
+ name: str
206
+ path: Path
207
+ running_version: str
208
+ installed_version: str | None
209
+ present: bool
210
+ modified: tuple[str, ...] = ()
211
+
212
+ @property
213
+ def unknown(self) -> bool:
214
+ """Installed, but with no manifest to say which version it is."""
215
+ return self.present and self.installed_version is None
216
+
217
+ @property
218
+ def stale(self) -> bool:
219
+ """Installed, known, and not the version now running."""
220
+ return self.installed_version is not None and self.installed_version != self.running_version
221
+
222
+ @property
223
+ def current(self) -> bool:
224
+ """Installed, known to match the running version, and unedited."""
225
+ return self.present and not self.unknown and not self.stale and not self.modified
226
+
227
+ def summary(self) -> str:
228
+ """One line naming the state and, where there is one, the fix."""
229
+ if not self.present:
230
+ return f"{self.name}: not installed at {self.path}"
231
+ if self.unknown:
232
+ return f"{self.name}: installed at {self.path}, version unknown (no manifest)"
233
+ if self.stale:
234
+ return (
235
+ f"{self.name}: installed {self.installed_version}, "
236
+ f"running {self.running_version} -- stale"
237
+ )
238
+ if self.modified:
239
+ return f"{self.name}: {self.running_version}, locally modified ({len(self.modified)})"
240
+ return f"{self.name}: {self.running_version}, up to date"
241
+
242
+ def to_dict(self) -> dict[str, Any]:
243
+ return {
244
+ "name": self.name,
245
+ "path": str(self.path),
246
+ "running_version": self.running_version,
247
+ "installed_version": self.installed_version,
248
+ "present": self.present,
249
+ "modified": list(self.modified),
250
+ "unknown": self.unknown,
251
+ "stale": self.stale,
252
+ "current": self.current,
157
253
  }
158
254
 
159
255
 
@@ -380,6 +476,7 @@ def install_skill(
380
476
  staged = staging / resolved.name
381
477
  previous = staging / "previous"
382
478
  _materialize(resolved, staged)
479
+ version = _write_manifest(resolved, staged)
383
480
  if replaced:
384
481
  # Move the old install aside rather than deleting it, so a failure
385
482
  # during the swap can put it back.
@@ -405,6 +502,139 @@ def install_skill(
405
502
  path=target_dir,
406
503
  files=resolved.files,
407
504
  replaced=replaced,
505
+ version=version,
506
+ )
507
+
508
+
509
+ def _running_version() -> str:
510
+ """The version of the library doing the installing.
511
+
512
+ Imported here rather than at module scope: ``adapt_agent/__init__.py``
513
+ pulls in the adapters, the detector and the optimizer, and this module is
514
+ otherwise cheap to import. Read from the package rather than restated,
515
+ because a second copy of a version string is a list beside a rule and this
516
+ codebase has paid for that five times already.
517
+ """
518
+ from adapt_agent import __version__
519
+
520
+ return __version__
521
+
522
+
523
+ def _digest(data: bytes) -> str:
524
+ return hashlib.sha256(data).hexdigest()
525
+
526
+
527
+ def _write_manifest(skill: Skill, destination: Path) -> str:
528
+ """Record what was installed into ``destination``. Returns the version.
529
+
530
+ The file list is derived from what was actually written, never restated:
531
+ a hand-kept second list is the failure this whole change exists to close.
532
+ """
533
+ version = _running_version()
534
+ digests = {}
535
+ for relative in skill.files:
536
+ digests[relative] = _digest(destination.joinpath(*relative.split("/")).read_bytes())
537
+ manifest = {
538
+ "skill": skill.name,
539
+ "version": version,
540
+ "installed_at": datetime.now(timezone.utc).isoformat(),
541
+ "files": digests,
542
+ }
543
+ (destination / MANIFEST_FILE).write_text(
544
+ json.dumps(manifest, indent=2, sort_keys=True) + "\n", encoding="utf-8"
545
+ )
546
+ return version
547
+
548
+
549
+ def installed_skill(path: str | Path) -> InstalledSkill | None:
550
+ """Read the manifest of an installed skill directory, or ``None``.
551
+
552
+ ``None`` covers every way a directory can fail to say what it is -- no
553
+ manifest, unreadable, not JSON, or JSON of the wrong shape. They are one
554
+ answer on purpose: each means the same thing to a caller, that the install
555
+ cannot be trusted to report its own version, and each has the same fix.
556
+
557
+ Never raises. This is a diagnostic, and a diagnostic that fails on a
558
+ damaged file tells you least exactly when you need it most.
559
+ """
560
+ manifest_path = Path(path) / MANIFEST_FILE
561
+ try:
562
+ raw = json.loads(manifest_path.read_text(encoding="utf-8"))
563
+ except (OSError, ValueError):
564
+ return None
565
+ if not isinstance(raw, dict):
566
+ return None
567
+ name, version = raw.get("skill"), raw.get("version")
568
+ if not isinstance(name, str) or not isinstance(version, str):
569
+ return None
570
+ files = raw.get("files")
571
+ digests = (
572
+ {k: v for k, v in files.items() if isinstance(k, str) and isinstance(v, str)}
573
+ if isinstance(files, dict)
574
+ else {}
575
+ )
576
+ installed_at = raw.get("installed_at")
577
+ return InstalledSkill(
578
+ name=name,
579
+ version=version,
580
+ installed_at=installed_at if isinstance(installed_at, str) else "",
581
+ path=Path(path),
582
+ digests=digests,
583
+ )
584
+
585
+
586
+ def skill_status(
587
+ skill: str | Skill,
588
+ destination: str | Path | None = None,
589
+ *,
590
+ target: str = "project",
591
+ root: str | Path | None = None,
592
+ ) -> SkillStatus:
593
+ """Compare an installed skill against the running library.
594
+
595
+ Resolves the destination exactly as :func:`install_skill` does, so what it
596
+ reports is what an install would replace.
597
+
598
+ An installed skill does not update itself when the library is upgraded, and
599
+ nothing in the directory said which version it was -- so a copy could sit
600
+ there feeding an agent guidance for a release two versions back, including
601
+ documented behaviour that had since been fixed, and the only way to find
602
+ out was to diff it against the wheel by hand. That happened.
603
+ """
604
+ resolved = skill if isinstance(skill, Skill) else get_skill(skill)
605
+ skills_dir = (
606
+ Path(destination) if destination is not None else default_destination(target, root=root)
607
+ )
608
+ path = skills_dir / resolved.name
609
+ running = _running_version()
610
+ if not path.is_dir():
611
+ return SkillStatus(
612
+ name=resolved.name,
613
+ path=path,
614
+ running_version=running,
615
+ installed_version=None,
616
+ present=False,
617
+ )
618
+ record = installed_skill(path)
619
+ modified: list[str] = []
620
+ if record is not None:
621
+ for relative, expected in sorted(record.digests.items()):
622
+ try:
623
+ actual = _digest(path.joinpath(*relative.split("/")).read_bytes())
624
+ except OSError:
625
+ # Unreadable or gone since install -- a difference from what was
626
+ # written, which is what `modified` means.
627
+ modified.append(relative)
628
+ continue
629
+ if actual != expected:
630
+ modified.append(relative)
631
+ return SkillStatus(
632
+ name=resolved.name,
633
+ path=path,
634
+ running_version=running,
635
+ installed_version=None if record is None else record.version,
636
+ present=True,
637
+ modified=tuple(modified),
408
638
  )
409
639
 
410
640
 
@@ -448,13 +678,18 @@ __all__ = [
448
678
  "MAX_DESCRIPTION_LENGTH",
449
679
  "MAX_NAME_LENGTH",
450
680
  "SKILL_FILE",
681
+ "MANIFEST_FILE",
451
682
  "InstallResult",
683
+ "InstalledSkill",
452
684
  "Skill",
685
+ "SkillStatus",
453
686
  "available_skills",
454
687
  "default_destination",
455
688
  "get_skill",
456
689
  "install_all",
457
690
  "install_skill",
691
+ "installed_skill",
458
692
  "parse_frontmatter",
693
+ "skill_status",
459
694
  "validate_skill",
460
695
  ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: adapt-agent
3
- Version: 0.3.1
3
+ Version: 0.3.2
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>
@@ -1,16 +1,24 @@
1
1
  # Releasing to PyPI
2
2
 
3
- Releases are cut by pushing a version tag. A GitHub Actions workflow
3
+ Releases are cut by **bumping `__version__` and merging to `main`**. A GitHub
4
+ Actions workflow
4
5
  ([`.github/workflows/release.yml`](https://github.com/CodeHalwell/ADAPT-Agent/blob/main/.github/workflows/release.yml))
5
- runs the full test matrix, builds and validates the distributions with
6
- [uv](https://docs.astral.sh/uv/), publishes them to PyPI with `uv publish`, and
7
- opens a GitHub Release.
6
+ notices the new version, runs the full test matrix, builds and validates the
7
+ distributions with [uv](https://docs.astral.sh/uv/), publishes them to PyPI with
8
+ `uv publish`, creates the `vX.Y.Z` tag, and opens a GitHub Release.
9
+
10
+ Pushing a tag by hand still works and does exactly the same thing:
8
11
 
9
12
  ```bash
10
13
  git tag -a v0.3.0 -m "v0.3.0"
11
14
  git push origin v0.3.0
12
15
  ```
13
16
 
17
+ **Automatic does not mean unattended.** The `pypi` environment's required
18
+ reviewer still has to approve before anything is uploaded — the bump removes
19
+ the tag ceremony, not the human. That approval is the backstop against a stray
20
+ version bump publishing itself, so keep the reviewer configured.
21
+
14
22
  Authentication uses **PyPI Trusted Publishing** (OIDC): GitHub proves the
15
23
  workflow's identity to PyPI directly, so there is no API token stored in this
16
24
  repository and nothing to rotate. That does require a one-time setup on PyPI —
@@ -79,18 +87,30 @@ dependency resolution.
79
87
  disagrees with it.
80
88
  2. **Update `CHANGELOG.md`.** Rename the `[Unreleased]` heading to the new
81
89
  version with today's date, and open a fresh `[Unreleased]` section above it.
82
- 3. **Merge that to `main`** through the usual PR flow, so CI has run on the
83
- exact commit you are about to tag.
84
- 4. **Tag and push:**
90
+ 3. **Merge that to `main`** through the usual PR flow. Merging is what starts
91
+ the release: the workflow asks PyPI whether the new version exists yet, and
92
+ publishes it when it does not.
93
+ 4. **Watch the run** under Actions → Release, and approve the publish step when
94
+ it pauses on the `pypi` environment.
95
+
96
+ That is the whole flow — no tag to push. The workflow creates `vX.Y.Z` itself,
97
+ after the upload succeeds, so a tag always means "this is on PyPI".
98
+
99
+ Pushing a tag by hand is still supported and takes the same path, which is
100
+ useful when you want the tag to exist before the publish, or to re-run a
101
+ release whose upload failed.
102
+
103
+ ### Why it asks PyPI rather than diffing the commit
85
104
 
86
- ```bash
87
- git checkout main && git pull
88
- git tag -a v0.3.0 -m "v0.3.0"
89
- git push origin v0.3.0
90
- ```
105
+ "Has this version already been released?" is answered by querying the index the
106
+ upload would go to, not by comparing `__version__` against the previous commit.
107
+ That answer stays correct for a revert, a re-run, a merge commit, a hand-pushed
108
+ tag, and a branch that lands out of order. If PyPI cannot be reached the answer
109
+ is *no release* — a missed release is recoverable, and a PyPI filename can never
110
+ be reused.
91
111
 
92
- 5. **Watch the run** under Actions → Release. If the `pypi` environment has a
93
- required reviewer, approve the publish step when it pauses.
112
+ An ordinary commit to `main` therefore costs one short job: the version check
113
+ runs, finds the version already published, and everything downstream is skipped.
94
114
 
95
115
  Versions follow [Semantic Versioning](https://semver.org/). A PEP 440
96
116
  pre-release suffix (`v0.3.0rc1`, `v0.3.0b1`, `v0.3.0.dev1`) is detected
@@ -103,14 +123,15 @@ after deletion — so the pipeline front-loads everything that could go wrong:
103
123
 
104
124
  | Stage | Check |
105
125
  | --- | --- |
106
- | `version` | The tag matches `adapt_agent.__version__`, so `v0.3.0` cannot ship `0.2.0`. |
107
- | `ci` | The same lint, type-check and 3.10–3.14 test matrix that guards `main`, re-run on the tagged commit. |
126
+ | `version` | Whether the version is already on PyPI — a published version is never republished. |
127
+ | `version` | On a hand-pushed tag, that the tag matches `adapt_agent.__version__`, so `v0.3.0` cannot ship `0.2.0`. |
128
+ | `ci` | The same lint, type-check and 3.10–3.14 test matrix that guards `main`, re-run on the released commit. |
108
129
  | `build` | Built with `uv build --no-sources`, then `twine check --strict` (via `uvx`) on both artifacts. |
109
130
  | `build` | The built filenames carry the expected version. |
110
131
  | `build` | The wheel really contains `SKILL.md` and its reference files, `py.typed`, and both console scripts; the sdist carries the skill too. |
111
132
  | `build` | A clean `uv venv` installs the wheel, runs `adapt --version`, and runs `adapt install skill` end to end. |
112
133
  | `publish-pypi` | `uv publish --trusted-publishing always` in the `pypi` environment. PEP 740 attestations are generated and uploaded by default, and `--check-url` makes a re-run skip files already on the index. |
113
- | `github-release` | Creates the GitHub Release with generated notes and attaches the exact artifacts that went to PyPI. |
134
+ | `github-release` | Creates the tag (when it does not already exist) and the GitHub Release, with generated notes and the exact artifacts that went to PyPI. |
114
135
 
115
136
  The skill and console-script assertions exist because those are *packaging
116
137
  data*, not code: if a `package-data` glob stopped matching, the tests would
@@ -60,6 +60,45 @@ Upgrading the library does not move the copy already on disk. Re-run
60
60
  `adapt install skill --force` after `pip install -U adapt-agent` to pick up the
61
61
  new version.
62
62
 
63
+ ## Checking an installation is current
64
+
65
+ Forgetting that re-run is silent, and the cost is not cosmetic: an agent reads
66
+ whatever `SKILL.md` is on disk, so a copy left behind by an old release keeps
67
+ prescribing its behaviour — including anything since fixed — with nothing in
68
+ the directory to say so. That happened in a real deployment, and it took a
69
+ hand diff against the wheel to find.
70
+
71
+ ```bash
72
+ adapt skills --check # exit 0 if current, 1 if missing, stale, or unversioned
73
+ adapt skills --check --target user
74
+ adapt skills --check --dir path/to/skills
75
+ adapt skills --check --json
76
+ ```
77
+
78
+ The exit code is the useful part: put it beside the lint step in CI and a
79
+ forgotten re-run fails the build instead of quietly shipping old guidance.
80
+
81
+ ```
82
+ adapt-agent: installed 0.2.0, running 0.3.2 -- stale
83
+
84
+ Install with: adapt install skill adapt-agent --force
85
+ ```
86
+
87
+ Each install records what produced it in a `.adapt-skill.json` manifest beside
88
+ the skill files, which is what `--check` reads. Four states are reported and
89
+ they need different fixes:
90
+
91
+ | State | Meaning | Fix |
92
+ | --- | --- | --- |
93
+ | up to date | matches the running library, unedited | none |
94
+ | stale | installed by an older release | `--force` reinstall |
95
+ | version unknown | no manifest — installed before manifests existed, or copied by hand | `--force` reinstall |
96
+ | locally modified | files differ from what was installed | your call; `--force` discards the edits |
97
+
98
+ A local edit is reported but does **not** fail the check. Editing your own copy
99
+ is a supported thing to do, and a check that broke the build over it would only
100
+ teach people to stop running the check.
101
+
63
102
  ## Listing what is bundled
64
103
 
65
104
  ```bash