agentframe-py 0.1.0__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/AGENTS.md +133 -29
  2. agentframe_py-0.2.0/CHANGELOG.md +195 -0
  3. agentframe_py-0.1.0/README.md → agentframe_py-0.2.0/PKG-INFO +130 -25
  4. agentframe_py-0.1.0/PKG-INFO → agentframe_py-0.2.0/README.md +94 -56
  5. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/docs/README.md +4 -2
  6. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/docs/api/README.md +12 -12
  7. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/docs/api/agents.md +3 -2
  8. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/docs/api/authorization.md +348 -9
  9. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/docs/api/configuration.md +10 -7
  10. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/docs/api/decorators.md +1 -1
  11. agentframe_py-0.2.0/docs/api/discovery.md +1095 -0
  12. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/docs/api/hosting.md +207 -33
  13. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/docs/api/runtime.md +81 -7
  14. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/docs/design/base-classes.md +3 -5
  15. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/docs/design/discovery.md +181 -109
  16. agentframe_py-0.2.0/docs/design/evaluation.md +487 -0
  17. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/docs/design/mcp-skills.md +1 -1
  18. agentframe_py-0.2.0/docs/design/orchestration.md +579 -0
  19. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/docs/design/tracing.md +23 -0
  20. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/docs/guides/agent-examples.md +74 -21
  21. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/docs/guides/annotations.md +1 -1
  22. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/docs/guides/authorization.md +92 -5
  23. agentframe_py-0.2.0/docs/guides/development.md +214 -0
  24. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/docs/guides/limits.md +73 -6
  25. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/docs/guides/releasing.md +5 -4
  26. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/docs/roadmap.md +120 -30
  27. agentframe_py-0.2.0/examples/multi_agent/__init__.py +1 -0
  28. agentframe_py-0.2.0/examples/multi_agent/agentframe.yaml +10 -0
  29. agentframe_py-0.2.0/examples/multi_agent/planner.py +129 -0
  30. agentframe_py-0.2.0/examples/multi_agent/research.py +108 -0
  31. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/examples/smoke.py +45 -1
  32. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/pyproject.toml +9 -2
  33. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/src/agentframe/__init__.py +47 -3
  34. agentframe_py-0.2.0/src/agentframe/__main__.py +5 -0
  35. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/src/agentframe/authoring/context.py +4 -0
  36. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/src/agentframe/authoring/definitions.py +20 -7
  37. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/src/agentframe/cli.py +73 -20
  38. agentframe_py-0.2.0/src/agentframe/compose.py +230 -0
  39. agentframe_py-0.2.0/src/agentframe/dev.py +180 -0
  40. agentframe_py-0.2.0/src/agentframe/dev_server.py +302 -0
  41. agentframe_py-0.2.0/src/agentframe/discovery/__init__.py +1 -0
  42. agentframe_py-0.2.0/src/agentframe/discovery/cards.py +181 -0
  43. agentframe_py-0.2.0/src/agentframe/discovery/directory.py +376 -0
  44. agentframe_py-0.2.0/src/agentframe/discovery/foundry.py +232 -0
  45. agentframe_py-0.2.0/src/agentframe/discovery/identity.py +317 -0
  46. agentframe_py-0.2.0/src/agentframe/discovery/registry.py +291 -0
  47. agentframe_py-0.2.0/src/agentframe/discovery/remote.py +840 -0
  48. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/src/agentframe/errors.py +58 -0
  49. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/src/agentframe/hosting/a2a_executor.py +161 -47
  50. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/src/agentframe/hosting/app.py +129 -23
  51. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/src/agentframe/hosting/card.py +9 -6
  52. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/src/agentframe/hosting/host.py +48 -8
  53. agentframe_py-0.2.0/src/agentframe/hosting/skill_call.py +222 -0
  54. agentframe_py-0.2.0/src/agentframe/project.py +127 -0
  55. agentframe_py-0.2.0/src/agentframe/runtime/correlation.py +147 -0
  56. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/src/agentframe/runtime/generation.py +16 -2
  57. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/src/agentframe/runtime/limits.py +7 -0
  58. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/src/agentframe/runtime/runtime.py +163 -8
  59. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/src/agentframe/security/authentication.py +7 -1
  60. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/src/agentframe/security/entra.py +109 -7
  61. agentframe_py-0.2.0/tests/integration/test_agentframe_dev.py +219 -0
  62. agentframe_py-0.2.0/tests/integration/test_compose_containers.py +131 -0
  63. agentframe_py-0.2.0/tests/integration/test_directory_localhost.py +56 -0
  64. agentframe_py-0.2.0/tests/integration/test_foundry_agents.py +136 -0
  65. agentframe_py-0.2.0/tests/integration/test_multi_agent_interop.py +283 -0
  66. agentframe_py-0.2.0/tests/integration/test_multi_agent_release.py +325 -0
  67. agentframe_py-0.2.0/tests/integration/test_on_behalf_of.py +390 -0
  68. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/integration/test_providers.py +37 -14
  69. agentframe_py-0.2.0/tests/integration/test_remote_agents.py +327 -0
  70. agentframe_py-0.2.0/tests/integration/test_remote_budgets.py +399 -0
  71. agentframe_py-0.2.0/tests/integration/test_remote_context.py +362 -0
  72. agentframe_py-0.2.0/tests/integration/test_remote_correlation.py +419 -0
  73. agentframe_py-0.2.0/tests/integration/test_remote_identity.py +190 -0
  74. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/support/a2a_client.py +23 -13
  75. agentframe_py-0.2.0/tests/support/a2a_peer.py +128 -0
  76. agentframe_py-0.2.0/tests/support/dev_targets.py +69 -0
  77. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/support/fake_entra.py +74 -1
  78. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/support/local_server.py +5 -2
  79. agentframe_py-0.2.0/tests/support/release_targets.py +113 -0
  80. agentframe_py-0.2.0/tests/unit/discovery/test_directory.py +546 -0
  81. agentframe_py-0.2.0/tests/unit/discovery/test_directory_find.py +220 -0
  82. agentframe_py-0.2.0/tests/unit/discovery/test_foundry_registry.py +414 -0
  83. agentframe_py-0.2.0/tests/unit/discovery/test_outbound_identity.py +148 -0
  84. agentframe_py-0.2.0/tests/unit/discovery/test_registry.py +294 -0
  85. agentframe_py-0.2.0/tests/unit/discovery/test_remote.py +1065 -0
  86. agentframe_py-0.2.0/tests/unit/golden/Dockerfile +9 -0
  87. agentframe_py-0.2.0/tests/unit/golden/compose.yaml +68 -0
  88. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/unit/hosting/test_admission.py +2 -1
  89. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/unit/hosting/test_app.py +29 -1
  90. agentframe_py-0.2.0/tests/unit/hosting/test_caller_context.py +458 -0
  91. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/unit/hosting/test_card.py +8 -1
  92. agentframe_py-0.2.0/tests/unit/runtime/test_correlation.py +106 -0
  93. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/unit/runtime/test_options.py +2 -0
  94. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/unit/security/test_entra.py +61 -0
  95. agentframe_py-0.2.0/tests/unit/test_compose.py +182 -0
  96. agentframe_py-0.2.0/tests/unit/test_dev.py +135 -0
  97. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/unit/test_package.py +1 -1
  98. agentframe_py-0.2.0/tests/unit/test_project.py +62 -0
  99. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/uv.lock +63 -1
  100. agentframe_py-0.1.0/CHANGELOG.md +0 -48
  101. agentframe_py-0.1.0/docs/api/discovery.md +0 -760
  102. agentframe_py-0.1.0/docs/guides/development.md +0 -117
  103. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/.github/actions/check-wheel/action.yml +0 -0
  104. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/.github/workflows/ci.yml +0 -0
  105. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/.github/workflows/foundry.yml +0 -0
  106. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/.github/workflows/release.yml +0 -0
  107. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/.gitignore +0 -0
  108. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/.pre-commit-config.yaml +0 -0
  109. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/.python-version +0 -0
  110. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/LICENSE +0 -0
  111. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/docs/api/models.md +0 -0
  112. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/docs/api/streaming.md +0 -0
  113. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/docs/design/prompt-safeguarding.md +0 -0
  114. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/docs/design/streaming.md +0 -0
  115. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/examples/__init__.py +0 -0
  116. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/examples/assistant.py +0 -0
  117. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/examples/echo.py +0 -0
  118. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/examples/orders.py +0 -0
  119. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/examples/routed_assistant.py +0 -0
  120. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/src/agentframe/authoring/__init__.py +0 -0
  121. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/src/agentframe/authoring/agent.py +0 -0
  122. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/src/agentframe/authoring/decorators.py +0 -0
  123. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/src/agentframe/authoring/services.py +0 -0
  124. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/src/agentframe/hosting/__init__.py +0 -0
  125. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/src/agentframe/hosting/admission.py +0 -0
  126. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/src/agentframe/hosting/dedup.py +0 -0
  127. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/src/agentframe/hosting/draining_server.py +0 -0
  128. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/src/agentframe/hosting/lifecycle.py +0 -0
  129. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/src/agentframe/hosting/model_router.py +0 -0
  130. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/src/agentframe/hosting/request_size.py +0 -0
  131. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/src/agentframe/hosting/routing.py +0 -0
  132. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/src/agentframe/models/__init__.py +0 -0
  133. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/src/agentframe/models/base.py +0 -0
  134. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/src/agentframe/models/foundry.py +0 -0
  135. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/src/agentframe/models/ollama.py +0 -0
  136. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/src/agentframe/models/strict_schema.py +0 -0
  137. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/src/agentframe/py.typed +0 -0
  138. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/src/agentframe/runtime/__init__.py +0 -0
  139. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/src/agentframe/runtime/options.py +0 -0
  140. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/src/agentframe/runtime/tools.py +0 -0
  141. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/src/agentframe/runtime/usage.py +0 -0
  142. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/src/agentframe/security/__init__.py +0 -0
  143. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/src/agentframe/security/authorization.py +0 -0
  144. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/src/agentframe/security/execution_policy.py +0 -0
  145. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/src/agentframe/security/principal.py +0 -0
  146. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/src/agentframe/testing.py +0 -0
  147. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/conftest.py +0 -0
  148. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/integration/test_entra_interoperability.py +0 -0
  149. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/integration/test_example.py +0 -0
  150. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/integration/test_graceful_shutdown.py +0 -0
  151. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/integration/test_hardening.py +0 -0
  152. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/integration/test_interoperability.py +0 -0
  153. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/integration/test_routing_example.py +0 -0
  154. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/integration/test_smoke.py +0 -0
  155. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/integration/test_structured_example.py +0 -0
  156. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/integration/test_success_criterion.py +0 -0
  157. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/integration/test_tools_example.py +0 -0
  158. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/support/cli_targets.py +0 -0
  159. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/unit/authoring/test_decorators.py +0 -0
  160. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/unit/authoring/test_services.py +0 -0
  161. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/unit/authoring/test_tool_registration.py +0 -0
  162. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/unit/authoring/test_tools.py +0 -0
  163. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/unit/hosting/test_a2a_binding.py +0 -0
  164. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/unit/hosting/test_a2a_executor.py +0 -0
  165. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/unit/hosting/test_dedup.py +0 -0
  166. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/unit/hosting/test_limits_ceiling.py +0 -0
  167. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/unit/hosting/test_model_router.py +0 -0
  168. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/unit/hosting/test_routing.py +0 -0
  169. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/unit/hosting/test_shutdown.py +0 -0
  170. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/unit/hosting/test_size_limits.py +0 -0
  171. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/unit/hosting/test_task_operations.py +0 -0
  172. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/unit/models/test_foundry.py +0 -0
  173. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/unit/models/test_models.py +0 -0
  174. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/unit/models/test_ollama.py +0 -0
  175. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/unit/models/test_strict_schema.py +0 -0
  176. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/unit/runtime/test_generation.py +0 -0
  177. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/unit/runtime/test_limits.py +0 -0
  178. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/unit/runtime/test_retries.py +0 -0
  179. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/unit/runtime/test_runtime.py +0 -0
  180. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/unit/runtime/test_structured_output.py +0 -0
  181. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/unit/runtime/test_tool_loop.py +0 -0
  182. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/unit/runtime/test_tool_replay.py +0 -0
  183. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/unit/runtime/test_validation.py +0 -0
  184. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/unit/security/test_authentication.py +0 -0
  185. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/unit/security/test_authorization.py +0 -0
  186. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/unit/security/test_execution_policy.py +0 -0
  187. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/unit/security/test_tool_authorization.py +0 -0
  188. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/unit/test_cli.py +0 -0
  189. {agentframe_py-0.1.0 → agentframe_py-0.2.0}/tests/unit/test_testing.py +0 -0
@@ -12,13 +12,16 @@ release (M8+), not an optional add-on or a hosting platform.
12
12
 
13
13
  This repository contains the M0 development tooling, M1 model-free
14
14
  authoring/local `Runtime`, M2 native A2A hosting, M3 Ollama/Foundry text
15
- generation, M4 typed structured outputs, M5 bounded tool calling and M6 built-in
16
- opt-in model routing alongside design documentation. There
17
- is no published package or stable API, and later-milestone features are not
18
- implemented. Keep design API examples beyond the implemented surface labeled
19
- as unimplemented; explicitly approved names/defaults
20
- are decisions, not still-unresolved recommendations. Do not scaffold implementation or
21
- add dependencies merely to make design snippets executable.
15
+ generation, M4 typed structured outputs, M5 bounded tool calling, M6 built-in
16
+ opt-in model routing and M7 hardening alongside design documentation. Version
17
+ 0.1.0, the first usable single-agent release, is published on PyPI as
18
+ `agentframe-py` (import `agentframe`). Pre-1.0 there is no stable API. The
19
+ M8+ multi-agent stories (#83-#97) are complete and form the 0.2.0
20
+ multi-agent release; later features (MCP, telemetry, streaming) are not implemented. Keep design API examples beyond
21
+ the implemented surface labeled as unimplemented; explicitly approved
22
+ names/defaults are decisions, not still-unresolved recommendations. Do not
23
+ scaffold implementation or add dependencies merely to make design snippets
24
+ executable.
22
25
 
23
26
  ## Code quality and readability
24
27
 
@@ -85,12 +88,13 @@ Folders stay one level deep. Put new code in the group that owns the concept:
85
88
  | Path | Contents |
86
89
  | --- | --- |
87
90
  | `src/agentframe/__init__.py` | Public API re-exports; every public name is importable from `agentframe`. |
88
- | `src/agentframe/` (root) | `errors.py`, `testing.py` (`ScriptedModel`), `cli.py` (`agentframe run`). |
91
+ | `src/agentframe/` (root) | `errors.py`, `testing.py` (`ScriptedModel`), `cli.py` (`agentframe run` / `dev` / `compose`), `project.py` (`agentframe.yaml`), `dev.py` (`dev_settings()`), `dev_server.py` (`agentframe dev` supervisor), `compose.py` (`agentframe compose` generator). |
89
92
  | `src/agentframe/authoring/` | `Agent`, `@agent` / `@skill`, definitions, `RunContext`, `Inject` / `Services`. |
90
93
  | `src/agentframe/runtime/` | `Runtime`, generation, `RunLimits`, `RunOptions`, `Usage`. |
91
94
  | `src/agentframe/models/` | `base.py` model protocol and settings; provider adapters `ollama.py`, `foundry.py`; `strict_schema.py` (OpenAI strict JSON-schema converter). |
92
95
  | `src/agentframe/hosting/` | `AgentApp`, `HostConfig`, Agent Card, A2A executor, routing. |
93
96
  | `src/agentframe/security/` | `Principal`, authentication, authorization. |
97
+ | `src/agentframe/discovery/` | `AgentRegistry`, `AgentReference`, `AgentQuery`, file and environment registries (M8+). |
94
98
  | `tests/unit/<group>/` | Unit tests mirroring the source groups; root-module tests in `tests/unit/`. |
95
99
  | `tests/integration/` | Interoperability, real providers, success criterion, README example. |
96
100
  | `tests/support/` | Shared helpers, on pytest's `pythonpath` (`from local_server import ...`). |
@@ -145,6 +149,17 @@ AGENTFRAME_TEST_FOUNDRY=1 AGENTFRAME_FOUNDRY_ENDPOINT=<base-url> \
145
149
 
146
150
  An enabled provider with a missing required variable fails rather than skips.
147
151
 
152
+ `tests/integration/test_foundry_agents.py` has opt-in `foundry` tests for
153
+ `FoundryAgentRegistry` (#86): it lists a real project with one A2A-enabled
154
+ agent and fetches that agent's card, read-only. It runs only with
155
+ `AGENTFRAME_TEST_FOUNDRY_AGENTS=1`, `AGENTFRAME_FOUNDRY_PROJECT_ENDPOINT`
156
+ (`https://<account>.services.ai.azure.com/api/projects/<project>`) and
157
+ `AGENTFRAME_FOUNDRY_A2A_AGENT` (the agent's name), after `az login`. Setup and
158
+ roles are in `docs/api/discovery.md#foundryagentregistry`. The same agent is the
159
+ real non-AgentFrame server for #94: one test `ask`s it once, another checks
160
+ `call` is refused before sending. In CI, `test_multi_agent_interop.py` proves
161
+ both directions against a plain SDK peer (`tests/support/a2a_peer.py`).
162
+
148
163
  `tests/integration/test_entra_interoperability.py` has one opt-in `entra` test
149
164
  using a real token from `az account get-access-token`, enabled with
150
165
  `AGENTFRAME_TEST_ENTRA=1`, `AGENTFRAME_ENTRA_TENANT_ID` and
@@ -248,6 +263,20 @@ downgrade to prompts. Implemented wire format (M2 #5): activate the card's
248
263
  skill-call URI via `A2A-Extensions` (echoed in the response), skill ID in message
249
264
  metadata `agentframe.skill`, exactly one JSON-object data part; malformed ->
250
265
  JSON-RPC InvalidParams, no task. Not a standard A2A `skill` field.
266
+ Implemented (#89): the same URI carries caller context; an activated message
267
+ without `agentframe.skill` is free text. Cards list `params.context`
268
+ (`deadline_ms`, `hops`, `max_total_tokens` since #90); senders send only
269
+ listed fields (0.1.0 lists none).
270
+ Receiver timeout = min(own, `agentframe.deadline_ms`); `RunContext.hops`;
271
+ `RunLimits.max_remote_hops=5`: caller fails before sending, deeper request
272
+ REJECTED with base `LimitExceededError` message; depth is cooperative.
273
+ Implemented (#91): context fields `root_invocation_id`/`parent_invocation_id`
274
+ (lowercase UUID strings; invalid -> ignored, new root, never echoed or an
275
+ error). Hosted runs log `Run started`/`Run completed` (INFO) with `extra`
276
+ `agent`, `invocation_id`, `root_invocation_id`, `parent_invocation_id`; no
277
+ log format set; root not exposed on `RunContext`. Valid W3C `traceparent`
278
+ (version 00) plus valid `tracestate` forwarded unchanged as headers to every
279
+ peer, invalid dropped; no spans or trace IDs created. Never authentication.
251
280
  Free-text/extension-unaware callers use custom or opt-in model routing plus
252
281
  validated extraction. Ambiguity/missing arguments asks back through
253
282
  `TASK_STATE_INPUT_REQUIRED`, never guesses; the caller answers in the same task,
@@ -400,9 +429,11 @@ call specialists over A2A, not a workflow/graph engine.
400
429
  `AgentRegistry` is a small read-only abstract base with proposed async
401
430
  `get(agent_id) -> AgentReference` and `list(query: AgentQuery) ->
402
431
  list[AgentReference]`; pagination is unresolved. Environment/file adapters read
403
- explicit sources. `FileAgentRegistry("agents.yaml")` reads a YAML name+URL list
404
- (detailed keys/schema open); `EnvironmentAgentRegistry("AGENTFRAME_AGENTS")`
405
- uses an explicit variable (payload encoding open).
432
+ explicit sources. Implemented (#83): `AgentReference(name, card_url)` (the Agent
433
+ Card URL), `AgentQuery(names=...)`; `FileAgentRegistry("agents.yaml")` reads a
434
+ YAML `name`/`url` list on each call (1 MiB, `safe_load`, PyYAML core dep);
435
+ `EnvironmentAgentRegistry("AGENTFRAME_AGENTS")` reads comma-separated
436
+ `name=url` pairs. Config problems and unknown names raise `DefinitionError`.
406
437
  `FoundryAgentRegistry` uses the chosen Foundry project agents API through
407
438
  `azure-ai-projects` 2.x (2.10.0 observed), project endpoint
408
439
  `https://{account}.services.ai.azure.com/api/projects/{project}`. Incoming A2A
@@ -416,15 +447,26 @@ Foundry A2A connections are a possible additional reference source, not decided.
416
447
  Model deployments are not agent endpoints; no AgentFrame-to-Foundry deployment
417
448
  or tested registry integration is claimed.
418
449
 
419
- Separate `AgentCardResolver` and library-managed `AgentDirectory` own native SDK
420
- card fetch/validation/cache/refresh and owned-resource lifecycle, not
421
- per-backend user HTTP loops. No import/construction I/O or implicit polling;
422
- background refresh is opt-in. Cache TTL defaults to 300 seconds; automatically
423
- refresh on stale access, failing unless `allow_stale=True` explicitly.
450
+ Implemented (#84): `AgentDirectory(sources=[...], card_ttl_seconds=300,
451
+ allow_stale=False, allow_http_hosts=(), http_client=None)` owns SDK card
452
+ fetch/validation/cache/refresh via an internal fetcher (no public
453
+ `AgentCardResolver`/`CardCacheConfig`). Sources read on every `get`; cards
454
+ cached per card URL, refetched when stale; failed refresh raises unless
455
+ `allow_stale=True` (`DiscoveredAgent.stale`, warning). https only except
456
+ loopback/`allow_http_hosts`, no redirects, 10 s, 1 MiB, interface URL same
457
+ origin as the registry `card_url`; no caller credentials. Remote failures
458
+ raise `RemoteAgentError`; duplicate names across sources `DefinitionError`.
459
+ No construction I/O or background tasks; closes only its own client.
424
460
  Compose `AgentDirectory(sources=[...])`, inject via `AgentApp(directory=...)`,
425
- and validate configured `Remote["name"]` bindings at startup.
461
+ and validate configured `Remote[Literal["name"]]` bindings at startup.
426
462
  `directory.find(skill="find_sources")` / `find(tags=["research"])` returns
427
463
  matching native cards from bounded validated enrichment, not assumed reference fields.
464
+ Implemented (#85): `find(*, skill=None, tags=())`, at least one filter; skill
465
+ ID and all-of tags must match on the same card skill; every source listed,
466
+ cards resolved through the cache, at most 8 fetches at a time; any source/card
467
+ failure raises (honouring `allow_stale`); source order; no pagination (small
468
+ curated sources). Found cards are not callable; orchestrators call configured
469
+ remotes, dynamic invocation is deferred.
428
470
  Inject directories as application dependencies;
429
471
  caller-owned resources stay caller-owned unless explicitly transferred. No
430
472
  registration writes, deploy/start/stop/delete, leases/federation, or A2A task
@@ -440,11 +482,47 @@ references/cards/source files; enforce application-selected outbound auth and
440
482
  destination/redirect policy, bounded reads/deadlines/cancellation and safe
441
483
  telemetry. Discovery does not delegate or auto-expose skills as tools. A separate
442
484
  `RemoteAgentClient` may remain underlying/explicit client with construction open.
443
- Author skills use `Remote["name"]` (excluded from external schemas),
485
+ Author skills use `Remote[Literal["name"]]` (excluded from external schemas),
444
486
  `call("skill", ..., output=list[Source], max_total_tokens=...)` for structured
445
487
  A2A, or `ask(text)` for any compatible peer. Both wait within caller deadline;
446
488
  remote input-required raises `ClarificationNeededError` with question, no
447
- automatic back-and-forth. See `docs/design/discovery.md` and `docs/api/discovery.md`.
489
+ automatic back-and-forth. Implemented (#87): `class Remote[N: str]` read from the
490
+ `Literal` (alias `type Research = Remote[Literal["research-agent"]]`; string form
491
+ fails mypy), `Runtime(directory=)`/`AgentApp(directory=)` (app-owned; no
492
+ directory -> construction `DefinitionError`), `Runtime.validate_remotes` at
493
+ startup, bound per run inside its deadline. `ask` sends via the SDK
494
+ `JsonRpcTransport` with the directory client to `interface_url` only,
495
+ `return_immediately` then `GetTask` polling (0.25 s -> 1 s), per-request
496
+ `A2A-Version`/timeout headers; deadline or cancellation sends best-effort
497
+ `CancelTask`.
498
+ Implemented (#88): `call(skill, /, *, output=None, **arguments)` checks the
499
+ card declares the skill-call extension and skill (else `RemoteAgentError`, no
500
+ send, no `ask` fallback), sends the M2 wire format with per-request
501
+ `A2A-Extensions`, reuses `ask`'s wait/cancel, decodes text or `{"value": ...}`
502
+ and validates `output` (`OutputValidationError`, no remote data).
503
+ Implemented (#90): `ask`/`call` take `max_total_tokens=` (default: all the run
504
+ has left), held atomically in the run's `RunAccounting` before sending and sent
505
+ as `agentframe.max_total_tokens` only to peers listing it in `params.context`
506
+ (older receivers reject unknown keys). Receiver uses min(own, incoming). The
507
+ task's usage snapshot `total_tokens` replaces the hold, even if higher;
508
+ non-advertising peers hold nothing and make usage unknown (`None`). Over the
509
+ remaining budget -> `TokenLimitExceededError` before sending (default-ceiling
510
+ message says to pass `max_total_tokens` per call for fan-out).
511
+ Implemented (#92): `remotes={name: Service(credential=...)}` on
512
+ `Runtime`/`AgentApp` (same rule as `services=`); `OutboundCredential` protocol
513
+ `async headers()` + `satisfies(scheme)`, built-ins `APIKeyCredential(key,
514
+ header="X-API-Key")`, `EntraAppCredential(credential, scope=...)`. Headers per
515
+ request (send, polls, cancel) to `interface_url` only; no redirects. Startup:
516
+ card security must be met by the entry, else `DefinitionError`; open cards need
517
+ none. Implemented (#93): `OnBehalfOf(scope=...)` entries; credential on
518
+ inbound `EntraIDAuth(client_id=audience, client_credential=secret|cert bytes|
519
+ assertion func)`; delegated tokens (`scp`, not `idtyp=app`) kept privately as
520
+ `VerifiedCaller.user_assertion`; one azure `OnBehalfOfCredential` per run.
521
+ Non-Entra host/no credential -> startup `DefinitionError`; no user (app-only,
522
+ local run) -> `PermissionDeniedError`; failed exchange -> `AuthenticationError`;
523
+ never a `Service` fallback. Opt-in `entra` OBO test uses two registrations.
524
+ `ClarificationNeededError(question, task_id, context_id)` fails a hosted
525
+ orchestrator task. See `docs/design/discovery.md` and `docs/api/discovery.md`.
448
526
 
449
527
  Plan cross-hop verified identity/delegation without authority amplification,
450
528
  permissions, deadlines, remaining token allowance and trace/correlation;
@@ -467,11 +545,32 @@ specialist with propagated context, and non-AgentFrame A2A client/server interop
467
545
  Development support starts with running N agents locally, then container
468
546
  packaging; Foundry/Kubernetes templates are later. M2 `agentframe run module:create_app`
469
547
  serves the app's own configured `AgentApp` (`--dev module:Class` is localhost-only
470
- anonymous; Uvicorn is the `server` extra). M8+ `agentframe dev` reads agentframe.yaml (names/modules/ports),
471
- starts localhost agents, writes local registry, configures dev API keys and
472
- combines logs tagged by agent/invocation. One container per agent; Dockerfile
473
- template and generated docker-compose example. Exact schema/generation/key
474
- lifecycle remains open; no actual config files before implementation.
548
+ anonymous; Uvicorn is the `server` extra). Implemented (#95): `agentframe dev`
549
+ reads agentframe.yaml (`agents: {name: {app: module:create_app, port}}`,
550
+ `project.py`), starts each as `python -m agentframe run` on 127.0.0.1 in file
551
+ order after the previous `/ready`, writes `.agentframe/agents.yaml` (name/url
552
+ only, self-gitignored), generates one random key per agent per session (an
553
+ agent calls others with its own key, verified as its name), passes keys only
554
+ via child env (`AGENTFRAME_DEV`, `AGENTFRAME_PUBLIC_URL`,
555
+ `AGENTFRAME_DEV_REGISTRY`, `AGENTFRAME_DEV_API_KEYS`,
556
+ `AGENTFRAME_DEV_REMOTE_KEYS`), never disk/logs; optional
557
+ `AGENTFRAME_DEV_CLIENT_KEY` is caller `developer`. Factories call
558
+ `dev_settings()` (refuses without `AGENTFRAME_DEV=1`; directory has its own
559
+ http client; reads `AGENTFRAME_ALLOW_HTTP_HOSTS`). Output prefixed
560
+ `[agent-name]`, agents log INFO; failures stop all and exit 1; Ctrl+C sends
561
+ one SIGTERM (children in own session), kills after 35 s; a client key with
562
+ `,`/`=` is refused. Implemented (#96): `agentframe compose [--config]
563
+ [--force]` (`compose.py`) writes development `compose.yaml` (one service per
564
+ agent, `agentframe run APP --host 0.0.0.0 --port P`, `127.0.0.1:P:P`, `/ready`
565
+ healthcheck, depends on earlier agents healthy; env = dev contract with
566
+ `AGENTFRAME_PUBLIC_URL=http://name:P`, `AGENTFRAME_AGENTS` (read by
567
+ `dev_settings()` as an `EnvironmentAgentRegistry`), `AGENTFRAME_DEV=1`,
568
+ `AGENTFRAME_ALLOW_HTTP_HOSTS`=service names; keys only
569
+ `${NAME_API_KEY:?...}`/`${AGENTFRAME_DEV_CLIENT_KEY:?...}` from user `.env`)
570
+ and, only if missing, a `Dockerfile` (digest-pinned `python:3.13-slim`,
571
+ `dist/*.whl`, `USER 10001:10001`); `--force` overwrites only `compose.yaml`.
572
+ Golden files in `tests/unit/golden/`; opt-in `docker` test
573
+ (`AGENTFRAME_TEST_DOCKER=1`, skipped without a running Docker).
475
574
  No hosting platform is implied. See `docs/guides/development.md`.
476
575
 
477
576
  ### Initial release boundaries
@@ -504,11 +603,15 @@ A2A failures use safe failed/rejected states as appropriate; no raw provider
504
603
  exception, stack trace or secret goes to callers. Log diagnostics with redaction:
505
604
  failed runs log ID, class and safe message; skill/tool/router tracebacks stay in
506
605
  operator logs with the ID, so authors keep secrets out of exception messages.
507
- `tests/integration/test_hardening.py` is the M7 end-to-end acceptance suite.
606
+ `tests/integration/test_hardening.py` is the M7 end-to-end acceptance suite;
607
+ `tests/integration/test_multi_agent_release.py` is M8+'s, running
608
+ `examples/multi_agent` as separate `agentframe run` processes.
508
609
  Network host examples must include clearly labeled application-supplied
509
610
  development authentication/authorization placeholders where appropriate;
510
611
  loopback/omission is not access policy or a production-safe default.
511
- M8+ adds `ClarificationNeededError` under `AgentFrameError`; local client
612
+ M8+ adds `RemoteAgentError` (#84; failures caused by another agent, optional
613
+ `task_id`/`task_state`) and `ClarificationNeededError` under
614
+ `AgentFrameError`; local client
512
615
  input-required question is not a failed remote task or durable resumption.
513
616
 
514
617
  Use explicit model selection, finite execution limits, validated inputs/outputs,
@@ -572,9 +675,10 @@ Read `README.md` and `docs/README.md` first. Keep `docs/roadmap.md`, the
572
675
  guides (`docs/guides/annotations.md`, `agent-examples.md`, `limits.md`,
573
676
  `authorization.md`, `development.md`) and the design docs
574
677
  (`docs/design/base-classes.md`, `tracing.md`, `mcp-skills.md`,
575
- `prompt-safeguarding.md`, `discovery.md`, `streaming.md`) consistent, including
576
- discovery/streaming API pages and navigation. Preserve the distinction between
577
- confirmed product direction and illustrative API details.
678
+ `prompt-safeguarding.md`, `discovery.md`, `streaming.md`, `evaluation.md`,
679
+ `orchestration.md`) consistent, including discovery/streaming API pages and
680
+ navigation. Preserve the distinction between confirmed product direction and
681
+ illustrative API details.
578
682
 
579
683
  Maintain `docs/api/README.md` and its linked surface reference pages alongside
580
684
  those design documents. Give every proposed decorator, class, and known public
@@ -0,0 +1,195 @@
1
+ # Changelog
2
+
3
+ All notable changes to AgentFrame are listed here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
+
6
+ Pre-1.0: public APIs may change in minor releases; changes are listed in the
7
+ changelog.
8
+
9
+ ## [Unreleased]
10
+
11
+ ## [0.2.0] - 2026-11-14
12
+
13
+ The multi-agent release (M8+): independently deployed AgentFrame agents find
14
+ each other through registries and an `AgentDirectory`, and orchestrator skills
15
+ call them over A2A with `Remote.call` and `Remote.ask`, carrying deadlines,
16
+ token budgets, hop limits, correlation and caller identity across hops.
17
+ `agentframe dev` and `agentframe compose` run several agents locally or in
18
+ containers. Calls interoperate with non-AgentFrame A2A agents, including
19
+ Microsoft Foundry agents.
20
+
21
+ ### Added
22
+
23
+ - **Multi-agent (M8+):** `examples/multi_agent`, a planner orchestrator and a
24
+ research agent with an `agentframe.yaml`. The planner finds the research
25
+ agent with `directory.find`, then fans out a structured `call` and a
26
+ free-text `ask` per topic in one `asyncio.TaskGroup`, each with its own
27
+ `max_total_tokens`. The release acceptance test runs both as separate
28
+ `agentframe run` processes; the examples smoke runner checks the example.
29
+ - **Multi-agent (M8+):** `agentframe compose` writes a development
30
+ `compose.yaml`, one container per agent of `agentframe.yaml`, and, if
31
+ missing, a `Dockerfile` template (`python:3.13-slim` pinned by digest,
32
+ non-root, installs the project's wheels from `dist/`). Services bind
33
+ `0.0.0.0` inside their container, publish on `127.0.0.1` only, check
34
+ `/ready` and get the `agentframe dev` environment with service-name URLs
35
+ and the explicit `AGENTFRAME_DEV=1` / `AGENTFRAME_ALLOW_HTTP_HOSTS` opt-in.
36
+ Keys appear only as `${VAR}` references to a `.env` file the user creates.
37
+ Existing files are never overwritten without `--force`, and a `Dockerfile`
38
+ never. `dev_settings()` now also reads an `AGENTFRAME_AGENTS` registry, and
39
+ `agentframe dev` refuses an `AGENTFRAME_DEV_CLIENT_KEY` containing `,` or
40
+ `=` without showing it.
41
+ - **Multi-agent (M8+):** `agentframe dev` runs every agent listed in
42
+ `agentframe.yaml` (`agents: {name: {app: module:create_app, port: int}}`)
43
+ as its own `agentframe run` process on `127.0.0.1`, wired together. It
44
+ checks ports first, starts agents in file order once each is ready, writes
45
+ `.agentframe/agents.yaml` with local card URLs, and generates one random
46
+ API key per agent per session, passed only through the agents'
47
+ environment, never written or logged. Every agent also accepts an optional
48
+ `AGENTFRAME_DEV_CLIENT_KEY` as caller `developer`. Output is combined with
49
+ an `[agent-name]` prefix, with each run's invocation IDs. Failures stop
50
+ every agent and exit 1; Ctrl+C stops them gracefully. App factories read the
51
+ wiring with the new `dev_settings()`, which refuses to run without
52
+ `AGENTFRAME_DEV=1`. `python -m agentframe` now runs the command line.
53
+ - **Multi-agent (M8+):** cross-agent token budgets. `Remote.ask` and
54
+ `Remote.call` take `max_total_tokens=` (default: everything the run has
55
+ left), held atomically against the run's `RunLimits.max_total_tokens`
56
+ before sending and sent as `agentframe.max_total_tokens` to peers whose
57
+ card lists it in `params.context` (now `deadline_ms`, `hops`,
58
+ `max_total_tokens`). Receivers use the smaller of it and their own limit.
59
+ The usage the peer's task reports is charged to the run once, so
60
+ `RunResult.usage` includes every hop; peers that do not list the field hold
61
+ nothing and leave usage unknown (`None`), never zero. A ceiling over what is
62
+ left raises `TokenLimitExceededError` before sending.
63
+ - **Multi-agent (M8+):** `OnBehalfOf(scope=...)` in `remotes=` calls a remote
64
+ agent on behalf of the signed-in user through the Microsoft Entra
65
+ on-behalf-of flow; the incoming token is never forwarded. `EntraIDAuth`
66
+ gains `client_id=`, `client_credential=` and `token_transport=`, and keeps a
67
+ delegated user token privately as `VerifiedCaller.user_assertion`. Startup
68
+ refuses `OnBehalfOf` behind non-Entra hosts or without a client credential;
69
+ app-only callers and local runs fail with `PermissionDeniedError`, failed
70
+ exchanges with `AuthenticationError`, never falling back to `Service`. The
71
+ `entra` extra now pins `azure-identity==1.27.0` and `aiohttp==3.14.5`.
72
+ - **Multi-agent (M8+):** cross-agent correlation. Hosted runs log `Run
73
+ started` / `Run completed` at `INFO`, and their log records carry `agent`,
74
+ `invocation_id`, `root_invocation_id` and `parent_invocation_id` in
75
+ `logging` `extra` (AgentFrame sets no format). `Remote.ask` and `call` send
76
+ `agentframe.root_invocation_id` and `agentframe.parent_invocation_id` to
77
+ peers whose card lists them in `params.context`, so every agent of one
78
+ request logs the same root. Values that are not lowercase UUID strings are
79
+ ignored (the run becomes a new root), never echoed or trusted. A valid W3C
80
+ `traceparent` (and valid `tracestate`) is forwarded unchanged to every peer;
81
+ invalid ones are dropped. None of it authenticates or grants access, and
82
+ task metadata is unchanged.
83
+ - **Multi-agent (M8+):** `Remote.ask` sends the run's remaining time
84
+ (`agentframe.deadline_ms`) and depth (`agentframe.hops`) to peers whose card
85
+ lists them in the skill-call extension's new `params.context`; others get
86
+ plain A2A. Receivers cap their timeout by the caller's deadline and reject
87
+ requests deeper than the new `RunLimits.max_remote_hops` (default 5); a run
88
+ at that depth fails before sending. New `RunContext.hops`. With the
89
+ extension activated, a message without `agentframe.skill` is now free text
90
+ instead of an `InvalidParams` error.
91
+ - **Discovery (M8+):** read-only `AgentRegistry` base with `AgentReference`
92
+ (agent name and Agent Card URL) and `AgentQuery` (name filter), plus
93
+ `FileAgentRegistry` (YAML `name`/`url` list) and `EnvironmentAgentRegistry`
94
+ (comma-separated `name=url` pairs). PyYAML is a new core dependency.
95
+ - **Discovery (M8+):** `AgentDirectory(sources=[...])` returns
96
+ `DiscoveredAgent` results with validated official SDK Agent Cards, cached
97
+ for 300 seconds, with `allow_stale`, `refresh`, `allow_http_hosts` and an
98
+ optional caller-owned `http_client`. New `RemoteAgentError` for failures
99
+ caused by another agent. `httpx` is now an exactly pinned core dependency.
100
+ - **Discovery (M8+):** `AgentDirectory.find(skill=..., tags=...)` returns the
101
+ Agent Cards of listed agents with a skill matching the ID and every tag,
102
+ in source order. It fetches at most eight cards at a time through the cache
103
+ and raises on any source or card failure instead of returning fewer cards.
104
+ - **Discovery (M8+):** `FoundryAgentRegistry(project_endpoint=...,
105
+ credential=...)` lists a Foundry project's A2A-enabled agents, mapped to
106
+ their A2A 1.0 Agent Card URLs. New optional `AgentRegistry.card_headers`
107
+ lets a source send its own credentials with its card requests; Foundry
108
+ sends a bearer token from its credential. The `foundry` extra now pins
109
+ `azure-ai-projects==2.11.0`.
110
+ - **Remote agents (M8+):** skills declare another A2A agent as a
111
+ `Remote[Literal["name"]]` parameter (excluded from arguments, the Agent Card
112
+ and schemas), found through `Runtime(directory=...)` or
113
+ `AgentApp(directory=...)`; server startup checks every name with
114
+ `Runtime.validate_remotes`. `await remote.ask(text)` sends plain A2A text and
115
+ returns the text answer within the run's deadline, cancelling the remote
116
+ task on deadline or cancellation. New `ClarificationNeededError` when the
117
+ remote agent asks a question.
118
+ - **Remote agents (M8+):** `await remote.call("skill", output=Type, **arguments)`
119
+ runs a remote AgentFrame agent's skill by ID through the skill-call
120
+ extension and validates its result into `output` (`OutputValidationError`
121
+ on mismatch). A peer whose card lacks the extension or the skill raises
122
+ `RemoteAgentError` before anything is sent, with no free-text fallback.
123
+ `output` and `max_total_tokens` cannot be remote argument names.
124
+ - **Outbound identity (M8+):** `remotes={name: Service(credential=...)}` on
125
+ `Runtime` and `AgentApp` authenticates remote calls with the calling
126
+ agent's own identity: `APIKeyCredential(key, header="X-API-Key")` or
127
+ `EntraAppCredential(credential, scope=...)` (any `azure.core` async token
128
+ credential, sent as a bearer token), or any `OutboundCredential`.
129
+ Credentials go per request only to the card's validated interface origin,
130
+ never with redirects, and incoming caller credentials are never forwarded.
131
+ Agents whose card declares security, including Foundry A2A agents, can now
132
+ be called; startup fails with `DefinitionError` when the configured
133
+ credential cannot satisfy the card's security.
134
+
135
+ ### Fixed
136
+
137
+ - `agentframe dev` no longer passes an exported `AGENTFRAME_AGENTS` to its
138
+ agents, which made `dev_settings()` list every agent twice and fail startup
139
+ with a duplicate-name `DefinitionError`.
140
+ - URLs with a non-numeric or out-of-range port are refused as
141
+ `HostConfig.public_url` and registry card URLs.
142
+ - `FileAgentRegistry` strips whitespace around names and URLs, like
143
+ `EnvironmentAgentRegistry`.
144
+ - Credential headers (`Authorization`, `Proxy-Authorization`, `Cookie`,
145
+ `X-API-Key`) are dropped from the A2A SDK's call context after
146
+ authentication, so the SDK's debug logs no longer contain bearer tokens or
147
+ API keys.
148
+ - `FoundryAgentRegistry` finds A2A-enabled agents again: the live Foundry API
149
+ lists them as `agent_endpoint.protocols: ["a2a", ...]`, which
150
+ `azure-ai-projects` 2.11.0 does not model, and its list response omits
151
+ `agent_endpoint` entirely, so every agent was skipped and lookups raised.
152
+ `list` now reads each queried agent's details (one call per agent), and the
153
+ `protocol_configuration.a2a` shape is still accepted.
154
+
155
+ ## [0.1.0] - 2026-11-13
156
+
157
+ The first usable single-agent release, distributed on PyPI as `agentframe-py`
158
+ and imported as `agentframe`.
159
+
160
+ ### Added
161
+
162
+ - **Authoring:** `Agent` subclasses with `@agent` identity and one async
163
+ `@skill` method per advertised skill; Python types and Pydantic models as
164
+ validated input and output contracts; `Inject[T]` services registered with
165
+ `Services.add` / `add_scoped`; per-invocation `RunContext`.
166
+ - **Local runtime:** `Runtime.invoke` returns a validated value and
167
+ `Runtime.run` a `RunResult` with invocation ID, usage, model and tool calls,
168
+ duration and finish reason. `RunLimits`, `ModelSettings` and `RunOptions`
169
+ resolve field by field from library to agent, skill and run.
170
+ - **A2A 1.0 hosting:** `AgentApp` serves a generated native Agent Card and a
171
+ JSON-RPC endpoint through the official `a2a-sdk`. Structured requests name a
172
+ skill through the Agent Card's skill-call extension; free text uses a custom
173
+ `SkillRouter` or the opt-in `ModelRouter`, which asks back with
174
+ `TASK_STATE_INPUT_REQUIRED` when a request is ambiguous.
175
+ `agentframe run module:create_app` serves an application (`server` extra).
176
+ - **Models:** `OllamaModel` (local Llama, `ollama` extra) and `FoundryModel`
177
+ (Microsoft Foundry GPT deployments, `foundry` extra), switched by
178
+ configuration only. One `ctx.generate` returns text, a validated Pydantic
179
+ instance with `output=`, or runs registered `@tool` functions with
180
+ `tools=[...]` within `max_tool_calls`. Refusal, truncation and unsupported
181
+ capabilities raise typed errors. `ScriptedModel` in `agentframe.testing`
182
+ scripts model replies for tests.
183
+ - **Security:** `HostConfig(auth=...)` with `APIKeyAuth`, `EntraIDAuth`
184
+ (`entra` extra), explicit `AllowAnonymous` or a custom `Authenticator`;
185
+ skill `requires_permissions`, role mapping and an injected
186
+ `AuthorizationPolicy`, failing closed; optional `ExecutionPolicy` input,
187
+ model-request, tool and output checks.
188
+ - **Hardening:** host ceilings for concurrent runs, request and response size,
189
+ and graceful shutdown; `/health` and `/ready`; A2A message deduplication by
190
+ caller and message ID; safe, correlated errors with redacted operator logs.
191
+ - **Examples:** runnable examples in `examples/`, each checked against the
192
+ installed wheel by `python -m examples.smoke`.
193
+
194
+ [0.2.0]: https://github.com/malcmiller/agentframe/releases/tag/v0.2.0
195
+ [0.1.0]: https://github.com/malcmiller/agentframe/releases/tag/v0.1.0