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.
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/CHANGELOG.md +61 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/PKG-INFO +1 -1
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/__init__.py +1 -1
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/cli/__init__.py +76 -1
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/skills/__init__.py +235 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent.egg-info/PKG-INFO +1 -1
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/docs/releasing.md +38 -17
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/docs/skill.md +39 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_cli_skills.py +121 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_skills.py +169 -1
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/CODE_OF_CONDUCT.md +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/CONTRIBUTING.md +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/LICENSE +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/MANIFEST.in +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/README.md +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/SECURITY.md +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/__main__.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/adapters/__init__.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/adapters/_governed.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/adapters/base.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/adapters/claude_agent/__init__.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/adapters/crewai/__init__.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/adapters/google_adk/__init__.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/adapters/langgraph/__init__.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/adapters/microsoft_agent_framework/__init__.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/adapters/openai_agents/__init__.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/adapters/pydantic_ai/__init__.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/adversarial/__init__.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/cli/__main__.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/core/__init__.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/core/governance.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/core/memory.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/core/middleware.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/core/policy.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/core/trust.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/core/types.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/evaluation/__init__.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/exceptions.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/integrations/__init__.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/integrations/_common.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/integrations/agent_framework.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/integrations/claude_agent.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/integrations/crewai.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/integrations/google_adk.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/integrations/langgraph.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/integrations/openai_agents.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/integrations/pydantic_ai.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/observability/__init__.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/__init__.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/config.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/dataset.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/evals.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/evaluation.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/extractors.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/introspection/__init__.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/introspection/claude_agent.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/introspection/crewai.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/introspection/google_adk.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/introspection/langgraph.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/introspection/microsoft_agent_framework.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/introspection/openai_agents.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/introspection/pydantic_ai.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/judge.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/judges.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/metrics.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/optimizers.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/parameters.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/performance.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/proposers.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/providers.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/retry.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/runners.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/optimization/target.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/patches/__init__.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/py.typed +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/security/__init__.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/security/firewall.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/security/taint_tracker.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/skills/adapt-agent/SKILL.md +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/skills/adapt-agent/references/evals.md +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/skills/adapt-agent/references/guardrails.md +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent/skills/adapt-agent/references/optimization.md +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent.egg-info/SOURCES.txt +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent.egg-info/dependency_links.txt +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent.egg-info/entry_points.txt +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent.egg-info/requires.txt +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/adapt_agent.egg-info/top_level.txt +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/docs/adapters.md +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/docs/api.md +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/docs/cli.md +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/docs/evals.md +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/docs/frameworks/claude_agent.md +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/docs/frameworks/crewai.md +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/docs/frameworks/google_adk.md +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/docs/frameworks/index.md +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/docs/frameworks/langgraph.md +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/docs/frameworks/microsoft_agent_framework.md +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/docs/frameworks/openai_agents.md +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/docs/frameworks/pydantic_ai.md +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/docs/index.md +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/docs/integrations.md +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/docs/observability.md +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/docs/optimization.md +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/docs/policy.md +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/docs/quickstart.md +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/docs/security.md +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/01_firewall_and_policy.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/02_adversarial_defense.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/03_langgraph_guarded_agent.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/04_observability.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/05_multi_framework_adapters.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/06_optimize_with_golden_dataset.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/07_train_from_yaml.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/08_agent_evals.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/README.md +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/claude_agent/01_basic_guarded.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/claude_agent/02_policy_observability_trust.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/claude_agent/03_evaluate_and_optimize.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/claude_agent/04_multi_agent_and_training.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/claude_agent/README.md +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/claude_agent/claude_agent.train.yaml +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/config.example.json +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/crewai/01_basic_guarded.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/crewai/02_policy_observability_trust.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/crewai/03_evaluate_and_optimize.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/crewai/04_multi_agent_and_training.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/crewai/README.md +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/crewai/crewai.train.yaml +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/google_adk/01_basic_guarded.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/google_adk/02_policy_observability_trust.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/google_adk/03_evaluate_and_optimize.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/google_adk/04_multi_agent_and_training.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/google_adk/README.md +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/google_adk/google_adk.train.yaml +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/langgraph/01_basic_guarded.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/langgraph/02_policy_observability_trust.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/langgraph/03_evaluate_and_optimize.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/langgraph/04_multi_agent_and_training.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/langgraph/README.md +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/langgraph/langgraph.train.yaml +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/microsoft_agent_framework/01_basic_guarded.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/microsoft_agent_framework/02_policy_observability_trust.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/microsoft_agent_framework/03_evaluate_and_optimize.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/microsoft_agent_framework/04_magentic_team_and_training.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/microsoft_agent_framework/README.md +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/microsoft_agent_framework/magentic.train.yaml +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/openai_agents/01_basic_guarded.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/openai_agents/02_policy_observability_trust.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/openai_agents/03_evaluate_and_optimize.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/openai_agents/04_multi_agent_and_training.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/openai_agents/README.md +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/openai_agents/openai_agents.train.yaml +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/pydantic_ai/01_basic_guarded.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/pydantic_ai/02_policy_observability_trust.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/pydantic_ai/03_evaluate_and_optimize.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/pydantic_ai/04_multi_agent_and_training.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/pydantic_ai/README.md +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/pydantic_ai/pydantic_ai.train.yaml +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/examples/train.example.yaml +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/pyproject.toml +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/setup.cfg +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/__init__.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_action_refs.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_adapters.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_adversarial.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_adversarial_extra.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_async_governance.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_cli.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_cli_optimize.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_core.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_evaluation.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_exceptions.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_firewall_extra.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_governed_adapters.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_integrations.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_memory.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_middleware.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_observability.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_observability_extra.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimization.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_config.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_dataset.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_evals.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_evaluation.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_extractors.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_introspect_claude_agent.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_introspect_crewai.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_introspect_google_adk.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_introspect_langgraph.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_introspect_microsoft_agent_framework.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_introspect_openai_agents.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_introspect_pydantic_ai.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_introspection.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_judge.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_judges.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_metrics.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_optimizers.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_parameters.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_proposers.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_providers.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_retry.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_runners.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_optimize_target.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_patches.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_policy.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_public_dataclass_compat.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_security.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_structured_evals.py +0 -0
- {adapt_agent-0.3.1 → adapt_agent-0.3.2}/tests/test_taint_tracker.py +0 -0
- {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.
|
|
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>
|
|
@@ -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.
|
|
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
|
|
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
|
|
6
|
-
[uv](https://docs.astral.sh/uv/), publishes them to PyPI with
|
|
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
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
93
|
-
|
|
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` |
|
|
107
|
-
| `
|
|
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
|
|
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
|