agentnode-sdk 0.11.3__tar.gz → 0.12.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 (184) hide show
  1. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/CHANGELOG.md +78 -0
  2. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/PKG-INFO +1 -1
  3. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/__init__.py +1 -1
  4. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/cli/main.py +2 -1
  5. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/cli/publish.py +39 -0
  6. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/cli/templates.py +15 -0
  7. agentnode_sdk-0.12.0/agentnode_sdk/runtimes/agent_llm_broker.py +83 -0
  8. agentnode_sdk-0.12.0/agentnode_sdk/runtimes/agent_llm_policy.py +184 -0
  9. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/runtimes/agent_runner.py +7 -0
  10. agentnode_sdk-0.12.0/agentnode_sdk/runtimes/agent_sandbox.py +218 -0
  11. agentnode_sdk-0.12.0/agentnode_sdk/sandbox/agent_container_wrapper.py +83 -0
  12. agentnode_sdk-0.12.0/agentnode_sdk/sandbox/agent_rpc.py +156 -0
  13. agentnode_sdk-0.12.0/agentnode_sdk/sandbox/agent_session.py +68 -0
  14. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/sandbox/backend.py +16 -0
  15. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/sandbox/container_backend.py +81 -1
  16. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/pyproject.toml +1 -1
  17. agentnode_sdk-0.12.0/spikes/agent_sandbox_routing/README.md +111 -0
  18. agentnode_sdk-0.12.0/spikes/agent_sandbox_routing/__init__.py +9 -0
  19. agentnode_sdk-0.12.0/spikes/agent_sandbox_routing/container_agent_wrapper.py +73 -0
  20. agentnode_sdk-0.12.0/spikes/agent_sandbox_routing/fake_llm.py +14 -0
  21. agentnode_sdk-0.12.0/spikes/agent_sandbox_routing/host_driver.py +145 -0
  22. agentnode_sdk-0.12.0/spikes/agent_sandbox_routing/trivial_agent.py +38 -0
  23. agentnode_sdk-0.12.0/tests/test_agent_llm_broker.py +179 -0
  24. agentnode_sdk-0.12.0/tests/test_agent_llm_policy.py +279 -0
  25. agentnode_sdk-0.12.0/tests/test_agent_rpc.py +186 -0
  26. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_agent_runner.py +84 -0
  27. agentnode_sdk-0.12.0/tests/test_agent_sandbox_e2e.py +175 -0
  28. agentnode_sdk-0.12.0/tests/test_agent_sandbox_routing.py +420 -0
  29. agentnode_sdk-0.12.0/tests/test_agent_sandbox_spike.py +87 -0
  30. agentnode_sdk-0.12.0/tests/test_agent_session_container.py +173 -0
  31. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_publish.py +90 -5
  32. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/.env.example +0 -0
  33. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/.gitignore +0 -0
  34. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/README.md +0 -0
  35. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/REGISTRY_SIGNING_ACTIVATION.md +0 -0
  36. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/REGISTRY_SIGNING_SPEC.md +0 -0
  37. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/THREAT_MODEL.md +0 -0
  38. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/TRUST_STACK.md +0 -0
  39. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode.lock +0 -0
  40. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/_fileutil.py +0 -0
  41. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/async_client.py +0 -0
  42. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/capability_graph.py +0 -0
  43. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/capability_taxonomy.py +0 -0
  44. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/cli/__init__.py +0 -0
  45. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/cli/__main__.py +0 -0
  46. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/cli/audit.py +0 -0
  47. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/cli/auth.py +0 -0
  48. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/cli/cassette_audit.py +0 -0
  49. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/cli/commands.py +0 -0
  50. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/cli/complements.py +0 -0
  51. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/cli/init.py +0 -0
  52. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/cli/mcp_commands.py +0 -0
  53. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/cli/mcp_status.py +0 -0
  54. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/cli/mcp_submit.py +0 -0
  55. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/cli/mcp_verify.py +0 -0
  56. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/cli/output.py +0 -0
  57. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/cli/record_cases.py +0 -0
  58. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/cli/sandbox_commands.py +0 -0
  59. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/cli/serve.py +0 -0
  60. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/cli/setup_wizard.py +0 -0
  61. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/cli/smart_run.py +0 -0
  62. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/cli/validate.py +0 -0
  63. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/cli/verify_local.py +0 -0
  64. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/client.py +0 -0
  65. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/compatibility.py +0 -0
  66. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/config.py +0 -0
  67. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/credential_handle.py +0 -0
  68. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/credential_resolver.py +0 -0
  69. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/credential_store.py +0 -0
  70. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/detect.py +0 -0
  71. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/exceptions.py +0 -0
  72. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/guard.py +0 -0
  73. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/input_guard.py +0 -0
  74. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/installer.py +0 -0
  75. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/key_status.py +0 -0
  76. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/lock_integrity.py +0 -0
  77. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/mcp_server.py +0 -0
  78. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/models.py +0 -0
  79. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/planner.py +0 -0
  80. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/policy.py +0 -0
  81. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/references.py +0 -0
  82. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/registry_trust.py +0 -0
  83. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/resolve.py +0 -0
  84. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/resource_provider.py +0 -0
  85. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/risk_profile.py +0 -0
  86. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/run_log.py +0 -0
  87. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/runner.py +0 -0
  88. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/runtime.py +0 -0
  89. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/runtimes/__init__.py +0 -0
  90. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/runtimes/mcp_runner.py +0 -0
  91. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/runtimes/python_runner.py +0 -0
  92. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/runtimes/remote_runner.py +0 -0
  93. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/sandbox/__init__.py +0 -0
  94. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/sandbox/policy.py +0 -0
  95. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/sandbox/types.py +0 -0
  96. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/signature.py +0 -0
  97. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/signing_key.py +0 -0
  98. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/agentnode_sdk/skill.py +0 -0
  99. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/sandbox-image/Dockerfile +0 -0
  100. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/sandbox-image/README.md +0 -0
  101. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/scripts/analyze_scores.py +0 -0
  102. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/scripts/batch_verify.py +0 -0
  103. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/scripts/ci_smoke_test.py +0 -0
  104. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/scripts/generate_compatibility_artifacts.py +0 -0
  105. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/scripts/verify_toolcalls.py +0 -0
  106. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/scripts/weekly_retest.sh +0 -0
  107. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/__init__.py +0 -0
  108. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/conftest.py +0 -0
  109. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_async_client.py +0 -0
  110. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_audit_ux.py +0 -0
  111. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_auto_upgrade_policy.py +0 -0
  112. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_cli.py +0 -0
  113. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_cli_lock.py +0 -0
  114. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_cli_run_resolution.py +0 -0
  115. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_client.py +0 -0
  116. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_client_json_guard.py +0 -0
  117. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_client_sprint_b.py +0 -0
  118. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_config.py +0 -0
  119. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_credential_handle.py +0 -0
  120. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_credential_integration.py +0 -0
  121. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_credential_resolver.py +0 -0
  122. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_credential_store.py +0 -0
  123. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_detect.py +0 -0
  124. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_detect_and_install.py +0 -0
  125. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_e2e_runtime.py +0 -0
  126. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_edge_cases.py +0 -0
  127. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_guard.py +0 -0
  128. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_guard_check.py +0 -0
  129. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_guard_config_cache.py +0 -0
  130. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_guard_policy.py +0 -0
  131. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_guard_preview.py +0 -0
  132. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_guard_schema.py +0 -0
  133. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_guard_set.py +0 -0
  134. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_guard_status.py +0 -0
  135. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_guard_tool_override.py +0 -0
  136. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_guard_tool_override_audit.py +0 -0
  137. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_guard_tool_override_cli.py +0 -0
  138. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_guard_ux.py +0 -0
  139. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_input_guard.py +0 -0
  140. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_input_guard_escalation.py +0 -0
  141. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_install_hardening.py +0 -0
  142. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_installer_sprint_b.py +0 -0
  143. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_intelligence.py +0 -0
  144. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_key_status.py +0 -0
  145. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_llm_binding.py +0 -0
  146. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_llm_call_runlog.py +0 -0
  147. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_lock_integrity.py +0 -0
  148. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_lock_runtime.py +0 -0
  149. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_mcp_audit.py +0 -0
  150. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_mcp_doctor.py +0 -0
  151. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_mcp_sandbox.py +0 -0
  152. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_mcp_server.py +0 -0
  153. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_observability.py +0 -0
  154. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_planner.py +0 -0
  155. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_policy.py +0 -0
  156. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_policy_integration.py +0 -0
  157. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_prompt_specs.py +0 -0
  158. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_provider_matrix.py +0 -0
  159. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_references.py +0 -0
  160. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_registry_trust.py +0 -0
  161. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_remote_hardening.py +0 -0
  162. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_remote_runner.py +0 -0
  163. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_resource_provider.py +0 -0
  164. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_resource_specs.py +0 -0
  165. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_risk_profile.py +0 -0
  166. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_run_log.py +0 -0
  167. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_runner.py +0 -0
  168. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_runtime.py +0 -0
  169. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_runtime_audit.py +0 -0
  170. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_sandbox_backend.py +0 -0
  171. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_sandbox_doctor.py +0 -0
  172. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_sandbox_e2e.py +0 -0
  173. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_sandbox_gate.py +0 -0
  174. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_security_hardening.py +0 -0
  175. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_signature.py +0 -0
  176. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_signing_key.py +0 -0
  177. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_skill.py +0 -0
  178. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_smart.py +0 -0
  179. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_stability.py +0 -0
  180. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_toolpack_sandbox.py +0 -0
  181. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_v02.py +0 -0
  182. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_validate.py +0 -0
  183. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/test_validate_skill.py +0 -0
  184. {agentnode_sdk-0.11.3 → agentnode_sdk-0.12.0}/tests/validation_lockfile.json +0 -0
@@ -1,5 +1,83 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.12.0 — Sandboxed community agents (flag-gated)
4
+
5
+ ### Added
6
+
7
+ - **Agent sandbox (default OFF):** with `AGENTNODE_AGENT_SANDBOX=1` (or config
8
+ `agent_sandbox.enabled: true`), `verified`/`unverified` community agents run
9
+ **sandbox-or-fail-closed** in the pinned container image — never on the host,
10
+ with **no host fallback** anywhere on the path. Tool calls cross an
11
+ allowlisted RPC back to the host's gated runner (the host owns allowlist and
12
+ limits); `trusted`/`curated` agents are unchanged. With the flag OFF
13
+ (default), community agents remain refused exactly as in 0.11.4.
14
+ - **Host-side LLM broker:** sandboxed agents request completions via RPC; the
15
+ provider call runs host-side and **provider API keys never enter the
16
+ container** (the container env is only `PYTHONPATH=/pack`).
17
+ - **`llm_access` manifest block (default-deny):** a sandboxed agent gets NO
18
+ host LLM unless its manifest declares `llm_access.enabled: true` — analogous
19
+ to `tool_access`. Caps: `max_calls`, `max_input_chars`, `max_output_chars`;
20
+ optional `allowed_models` checks the HOST-chosen model (the agent never picks
21
+ a model; absent = unrestricted, `[]` = refuse-all, manifest+host = both must
22
+ allow). The host ceiling (`agent_sandbox.llm` in `~/.agentnode/config.json`)
23
+ always wins — it can lower caps, restrict models, or disable access entirely.
24
+ Refused/failed LLM calls return **graceful per-call errors** the agent can
25
+ catch; they never crash the run and never fall back to the host.
26
+ - **Audit:** every sandboxed run writes ONE aggregated, sanitized record to
27
+ `~/.agentnode/audit.jsonl` (`event: agent_run`, `source: agent_sandbox`) —
28
+ counters, caps, and fixed reason codes only; **never prompts, keys, raw
29
+ provider errors, or agent-authored error text**. Fail-closed refusals
30
+ (missing volume/runtime, session-start failure) are audited too.
31
+ - Agent manifest template (`agentnode init`) now documents the opt-in
32
+ `llm_access` block with the caps and `allowed_models`.
33
+
34
+ ### Changed
35
+
36
+ - `agentnode init` agent template includes the `llm_access` example (newly
37
+ scaffolded packages only; existing manifests are unaffected — an absent
38
+ `llm_access` simply means deny).
39
+
40
+ ### Hardened
41
+
42
+ - The sandbox path is fail-closed end to end: missing/stale volume, missing
43
+ container runtime or pinned image, sandbox-start failure, or a host-loop
44
+ error all return a clean `sandbox_unavailable`/error result — community
45
+ agent code never executes on the host.
46
+ - LLM broker errors are generic and leak-free (no key, no provider internals,
47
+ no prompt echo). A model-allowlist refusal never calls the provider (no
48
+ charge), and the host-side model name is never sent into the sandbox.
49
+
50
+ ### BREAKING / Upgrade Notes
51
+
52
+ - **None.** With the flag OFF (default), behavior is identical to 0.11.4.
53
+ There are no flag-ON users yet (the flag ships first in this release).
54
+ - Enabling the agent sandbox requires a container runtime plus the pinned
55
+ public sandbox image — `agentnode sandbox pull` to fetch it,
56
+ `agentnode sandbox doctor` for diagnosis.
57
+ - Operational note for managed hosts (e.g. Coolify): automatic image pruning
58
+ can remove the pinned sandbox image, degrading community execution to
59
+ fail-closed until it is re-pulled. Keep the image pinned (e.g. a minimal
60
+ keep-alive holder container referencing the digest) or re-pull on a
61
+ schedule.
62
+
63
+ ## 0.11.4 — Publish confirm gate
64
+
65
+ ### Added
66
+
67
+ - **`agentnode publish` now asks for confirmation before publishing.** After the
68
+ preview, the command prompts `Publish <pkg>@<version> to <registry>? [y/N]`
69
+ (default No) and only uploads on explicit `y`. A new `--yes`/`-y` flag skips
70
+ the prompt for CI/automation. `--dry-run` is unchanged (never prompts, never
71
+ publishes). Prevents accidental publishes of the wrong package/version/folder.
72
+
73
+ ### BREAKING / Upgrade Notes
74
+
75
+ - **Non-interactive publish now requires `--yes`.** Previously `agentnode publish`
76
+ in a non-interactive context (CI, piped stdin, or `AGENTNODE_NON_INTERACTIVE=1`)
77
+ uploaded silently. It now **refuses** without `--yes` and exits non-zero.
78
+ Automation that publishes must add `--yes`. Interactive use is unaffected
79
+ beyond the new prompt.
80
+
3
81
  ## 0.11.3 — Test hygiene + multi-tool run guidance
4
82
 
5
83
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentnode-sdk
3
- Version: 0.11.3
3
+ Version: 0.12.0
4
4
  Summary: Python SDK for AgentNode — the open upgrade and discovery infrastructure for AI agents.
5
5
  Project-URL: Homepage, https://agentnode.net
6
6
  Project-URL: Repository, https://github.com/agentnode-ai/agentnode
@@ -38,7 +38,7 @@ from agentnode_sdk.runtime import AgentNodeRuntime
38
38
  Client = AgentNodeClient
39
39
  ToolError = AgentNodeToolError
40
40
 
41
- __version__ = "0.11.3"
41
+ __version__ = "0.12.0"
42
42
  __all__ = [
43
43
  "AgentNode",
44
44
  "AsyncAgentNode",
@@ -154,6 +154,7 @@ def main(argv: list[str] | None = None) -> int:
154
154
  publish_parser.add_argument("--dry-run", action="store_true", help="Validate and build without uploading")
155
155
  publish_parser.add_argument("--skip-validate", action="store_true", help="Continue despite validation errors")
156
156
  publish_parser.add_argument("--token", default=None, help="API key (default: AGENTNODE_API_KEY env)")
157
+ publish_parser.add_argument("--yes", "-y", action="store_true", help="Skip the confirmation prompt (required for non-interactive/CI publish)")
157
158
 
158
159
  # record-cases
159
160
  record_parser = sub.add_parser("record-cases", help="Record VCR cassettes for API verification cases")
@@ -338,7 +339,7 @@ def main(argv: list[str] | None = None) -> int:
338
339
  return commands.cmd_verify_local(args.path)
339
340
  if args.command == "publish":
340
341
  from agentnode_sdk.cli.publish import cmd_publish
341
- return cmd_publish(args.path, dry_run=args.dry_run, skip_validate=args.skip_validate, token=args.token)
342
+ return cmd_publish(args.path, dry_run=args.dry_run, skip_validate=args.skip_validate, token=args.token, yes=args.yes)
342
343
  if args.command == "record-cases":
343
344
  return commands.cmd_record_cases(args.path, strict=args.strict)
344
345
  if args.command == "inspect":
@@ -286,12 +286,38 @@ def _post_publish(
286
286
  }
287
287
 
288
288
 
289
+ def _confirm_publish(
290
+ pkg_id: str, version: str, registry: str, *, yes: bool, interactive: bool
291
+ ) -> bool:
292
+ """Final consent gate before an external publish. Returns True to proceed.
293
+
294
+ - ``yes``: explicit bypass for CI/automation → proceed.
295
+ - not ``interactive``: refuse — never publish non-interactively without --yes.
296
+ - interactive: prompt ``[y/N]``, default No.
297
+ """
298
+ if yes:
299
+ return True
300
+ if not interactive:
301
+ print(
302
+ " Refusing to publish non-interactively. "
303
+ "Re-run with --yes to confirm in CI/automation.",
304
+ file=sys.stderr,
305
+ )
306
+ return False
307
+ answer = input(f" Publish {pkg_id}@{version} to {registry}? [y/N] ").strip().lower()
308
+ if answer in ("y", "yes"):
309
+ return True
310
+ print(" Publish cancelled.")
311
+ return False
312
+
313
+
289
314
  def cmd_publish(
290
315
  path_str: str,
291
316
  *,
292
317
  dry_run: bool = False,
293
318
  skip_validate: bool = False,
294
319
  token: str | None = None,
320
+ yes: bool = False,
295
321
  ) -> int:
296
322
  """Publish a package to the AgentNode registry."""
297
323
  from agentnode_sdk.cli.validate import validate_package_dir
@@ -389,6 +415,19 @@ def cmd_publish(
389
415
  )
390
416
  return 1
391
417
 
418
+ # Publish confirm gate — publish is an external, hard-to-undo action.
419
+ # Detect interactivity robustly (CI/pipes have a non-TTY stdin even without
420
+ # AGENTNODE_NON_INTERACTIVE set); non-interactive requires explicit --yes.
421
+ registry = _resolve_api_base().replace("https://", "").replace("http://", "")
422
+ interactive = sys.stdin.isatty() and os.environ.get(
423
+ "AGENTNODE_NON_INTERACTIVE", ""
424
+ ).lower() not in ("true", "1")
425
+ if not _confirm_publish(pkg_id, version, registry, yes=yes, interactive=interactive):
426
+ # interactive decline = clean cancel (0); non-interactive without --yes
427
+ # = refusal (1). When yes=True the gate always proceeds, so we never land
428
+ # here in that case.
429
+ return 0 if interactive else 1
430
+
392
431
  try:
393
432
  sig_block = _sign_for_publish(pkg_id, manifest, artifact_bytes)
394
433
  manifest["_signatures"] = {"publisher": [sig_block]}
@@ -655,6 +655,21 @@ agent:
655
655
  tier: "llm_only"
656
656
  llm:
657
657
  required: true
658
+ llm_access:
659
+ # Host-brokered LLM access for SANDBOXED community runs (opt-in).
660
+ # When your agent runs sandboxed on someone else's machine, it can only
661
+ # reach their LLM credentials through the host broker, and only if you
662
+ # set enabled: true here. Default false = no access (calls fail gracefully).
663
+ # The host's own ceiling (agent_sandbox.llm in the host config) ALWAYS
664
+ # wins: it can lower these caps or disable access entirely.
665
+ enabled: false
666
+ max_calls: 20
667
+ max_input_chars: 24000
668
+ max_output_chars: 24000
669
+ # Optionally restrict which host-chosen models may serve this agent
670
+ # (the agent cannot pick a model; the host does). Omit = any host model.
671
+ # An explicit empty list means no model is acceptable.
672
+ # allowed_models: ["gpt-4o-mini", "claude-3-5-haiku-latest"]
658
673
  system_prompt: |
659
674
  You are a helpful agent that accomplishes goals step by step.
660
675
  Think carefully, use available tools, and return a clear result.
@@ -0,0 +1,83 @@
1
+ """Host-side LLM broker for sandboxed agents (B2b-1).
2
+
3
+ A sandboxed community agent requests an LLM completion via the ``call_llm`` RPC;
4
+ the HOST runs the actual provider call here. The provider API key stays host-side
5
+ and NEVER enters the sandbox — the broker receives only ``messages`` and returns
6
+ only a structured completion. A PURE RELAY: it never interprets messages as host
7
+ commands. Errors are GENERIC — the raw provider exception (which can contain the
8
+ key, request URLs, or internal details) is never surfaced to the agent.
9
+
10
+ Credential policy (caps, default-deny, audit) lives in ``agent_llm_policy`` —
11
+ this module is only the secure provider plumbing. C2 adds one policy hook here:
12
+ an optional ``allowed_models`` check, because the effective model (incl. the
13
+ default fallback) is only known at this point.
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import logging
18
+
19
+ logger = logging.getLogger("agentnode.agent_sandbox")
20
+
21
+ # Used only when the host LLM binding does not specify a model.
22
+ _DEFAULT_MODELS = {
23
+ "openai": "gpt-4o-mini",
24
+ "anthropic": "claude-3-5-haiku-latest",
25
+ }
26
+
27
+
28
+ class LlmBrokerError(RuntimeError):
29
+ """Generic, leak-free LLM broker failure (no key, no provider internals)."""
30
+
31
+
32
+ class LlmModelNotAllowedError(LlmBrokerError):
33
+ """C2: the host-chosen model is outside the effective ``allowed_models`` set.
34
+
35
+ The MESSAGE stays generic (it crosses into the sandbox); the host-side model
36
+ name travels only on the ``model`` attribute, for audit.
37
+ """
38
+
39
+ def __init__(self, model: str = ""):
40
+ super().__init__("the host-configured LLM model is not allowed for this agent")
41
+ self.model = model
42
+
43
+
44
+ def host_llm_broker(messages: list, *, allowed_models=None) -> dict:
45
+ """Run one LLM completion HOST-side and return ``{"role","content"}``.
46
+
47
+ ``allowed_models`` (optional, C2 defense-in-depth): a set of model ids the
48
+ host-chosen model must be in — the agent never picks a model, this only
49
+ checks the one the host resolved (incl. the default fallback). Not allowed →
50
+ :class:`LlmModelNotAllowedError` WITHOUT calling the provider.
51
+
52
+ Raises :class:`LlmBrokerError` (generic) on any failure — no provider /
53
+ missing SDK / provider exception — never leaking the key or the raw provider
54
+ exception text.
55
+ """
56
+ from agentnode_sdk.runtimes.agent_runner import _auto_detect_llm
57
+
58
+ binding = _auto_detect_llm()
59
+ if not binding:
60
+ raise LlmBrokerError("no LLM provider configured on the host")
61
+
62
+ client = binding.get("client")
63
+ provider = binding.get("provider")
64
+ model = binding.get("model") or _DEFAULT_MODELS.get(provider or "", "")
65
+ if client is None or provider not in _DEFAULT_MODELS:
66
+ raise LlmBrokerError("unsupported or unavailable LLM provider")
67
+
68
+ if allowed_models is not None and model not in allowed_models:
69
+ logger.warning("LLM broker refused a model outside allowed_models")
70
+ raise LlmModelNotAllowedError(model)
71
+
72
+ try:
73
+ if provider == "openai":
74
+ resp = client.chat.completions.create(model=model, messages=list(messages or []))
75
+ content = resp.choices[0].message.content
76
+ else: # anthropic
77
+ resp = client.messages.create(model=model, max_tokens=1024, messages=list(messages or []))
78
+ content = resp.content[0].text
79
+ except Exception as exc: # never surface the raw provider error (may carry the key)
80
+ logger.warning("LLM broker provider call failed: %s", type(exc).__name__)
81
+ raise LlmBrokerError("LLM provider call failed")
82
+
83
+ return {"role": "assistant", "content": content or ""}
@@ -0,0 +1,184 @@
1
+ """C1 — host-credential LLM broker policy for sandboxed community agents.
2
+
3
+ This protects the HOST USER's LLM credentials/wallet from UNTRUSTED
4
+ (verified/unverified) sandboxed community code that borrows the host key via the
5
+ broker. It is NOT a limit on agents in general: trusted/curated/self-run agents
6
+ (own key, host path) are unaffected — they keep "run until done".
7
+
8
+ DEFAULT-DENY: like ``tool_access``, an agent reaches the host LLM only if it
9
+ explicitly declares ``llm_access.enabled: true`` in its manifest. The host-config
10
+ ceiling (``agent_sandbox.llm`` in ~/.agentnode/config.json) ALWAYS wins — it can
11
+ lower the caps or force-disable, and a higher manifest value is clamped to it.
12
+
13
+ Refusals/errors come back as a STRUCTURED ``{"ok": False, "error": ...}`` so the
14
+ RPC host can return them as graceful per-call errors the agent can catch — never
15
+ a whole-run crash, never a host fallback. Errors are sanitized: no key, no prompt,
16
+ no raw provider internals.
17
+ """
18
+ from __future__ import annotations
19
+
20
+ from dataclasses import dataclass
21
+
22
+ from agentnode_sdk.runtimes.agent_llm_broker import LlmBrokerError, LlmModelNotAllowedError
23
+
24
+ # Conservative built-in ceilings: the fallback when the host config does not set a
25
+ # field, and the default the manifest request is clamped against.
26
+ _DEFAULT_MAX_CALLS = 20
27
+ _DEFAULT_MAX_INPUT_CHARS = 24_000
28
+ _DEFAULT_MAX_OUTPUT_CHARS = 24_000
29
+
30
+
31
+ @dataclass(frozen=True)
32
+ class LlmAccessPolicy:
33
+ """Resolved per-run LLM access policy for one sandboxed community agent.
34
+
35
+ ``allowed_models`` (C2): ``None`` = unrestricted (the host picks the model
36
+ anyway); a frozenset = only those host-chosen models may serve the agent
37
+ (an empty set refuses all — same convention as tool_access.allowed_packages).
38
+ """
39
+
40
+ enabled: bool = False
41
+ max_calls: int = 0
42
+ max_input_chars: int = 0
43
+ max_output_chars: int = 0
44
+ allowed_models: frozenset | None = None
45
+
46
+
47
+ def _ceiling(host_llm: dict, key: str, default: int) -> int:
48
+ """The host-config ceiling for ``key`` (a positive int), else the built-in."""
49
+ try:
50
+ v = int(host_llm[key])
51
+ except (KeyError, TypeError, ValueError):
52
+ return int(default)
53
+ return v if v > 0 else int(default)
54
+
55
+
56
+ def _request(manifest: dict, key: str, ceiling: int) -> int:
57
+ """The manifest's requested value for ``key`` (positive int), else the ceiling."""
58
+ try:
59
+ v = int(manifest[key])
60
+ except (KeyError, TypeError, ValueError):
61
+ return ceiling
62
+ return v if v > 0 else ceiling
63
+
64
+
65
+ def _models_set(section: dict) -> frozenset | None:
66
+ """``allowed_models`` as a frozenset, or ``None`` when absent / not a list.
67
+
68
+ Convention (mirrors ``tool_access.allowed_packages``): absent → ``None`` =
69
+ unrestricted; an explicit ``[]`` → empty set = no model is acceptable.
70
+ """
71
+ raw = section.get("allowed_models")
72
+ if not isinstance(raw, list):
73
+ return None
74
+ return frozenset(s for s in (str(x).strip() for x in raw) if s)
75
+
76
+
77
+ def resolve_llm_policy(agent_config: dict | None, host_config: dict | None = None) -> LlmAccessPolicy:
78
+ """Resolve the effective policy = min(manifest request, host ceiling).
79
+
80
+ Default-deny: a missing ``llm_access`` or ``enabled != true`` → disabled. The
81
+ host config can also force-disable (``agent_sandbox.llm.enabled: false``).
82
+ ``allowed_models``: both sides set → intersection (both must allow); one side
83
+ set → that side; neither → unrestricted.
84
+ """
85
+ manifest = ((agent_config or {}).get("llm_access")) or {}
86
+ host_llm = ((((host_config or {}).get("agent_sandbox")) or {}).get("llm")) or {}
87
+
88
+ manifest_enabled = manifest.get("enabled") is True
89
+ host_enabled = host_llm.get("enabled", True) # host omits → defer to manifest
90
+ if not (manifest_enabled and host_enabled is not False):
91
+ return LlmAccessPolicy(enabled=False)
92
+
93
+ mc = _ceiling(host_llm, "max_calls", _DEFAULT_MAX_CALLS)
94
+ ic = _ceiling(host_llm, "max_input_chars", _DEFAULT_MAX_INPUT_CHARS)
95
+ oc = _ceiling(host_llm, "max_output_chars", _DEFAULT_MAX_OUTPUT_CHARS)
96
+ m, h = _models_set(manifest), _models_set(host_llm)
97
+ models = (m & h) if (m is not None and h is not None) else (m if m is not None else h)
98
+ return LlmAccessPolicy(
99
+ enabled=True,
100
+ max_calls=min(_request(manifest, "max_calls", mc), mc),
101
+ max_input_chars=min(_request(manifest, "max_input_chars", ic), ic),
102
+ max_output_chars=min(_request(manifest, "max_output_chars", oc), oc),
103
+ allowed_models=models,
104
+ )
105
+
106
+
107
+ def _input_chars(messages) -> int:
108
+ return sum(len(str((m or {}).get("content", "") or "")) for m in (messages or []))
109
+
110
+
111
+ def make_policy_broker(policy: LlmAccessPolicy, base_broker):
112
+ """Wrap ``base_broker`` (the host LLM broker) with policy enforcement.
113
+
114
+ Returns a callable ``(messages) -> {"ok": bool, "completion"?|"error"?}``.
115
+ Per-run state (the call counter) lives in this closure, so one run gets one
116
+ fresh budget. Never raises — all failures are structured + sanitized so the
117
+ RPC host turns them into graceful per-call errors (no host fallback).
118
+
119
+ C2: the callable exposes ``broker.usage`` (live per-run counters for the
120
+ aggregated audit record — counts, reason codes, and at most the HOST-side
121
+ model name; never message content) and ``broker.policy`` (the resolved
122
+ policy). ``allowed_models`` is forwarded to ``base_broker`` ONLY when the
123
+ policy sets it, so plain single-arg brokers/fakes keep working unchanged.
124
+ """
125
+ state = {
126
+ "requests": 0,
127
+ "calls": 0,
128
+ "ok": 0,
129
+ "refused_disabled": 0,
130
+ "refused_limit": 0,
131
+ "refused_input": 0,
132
+ "refused_output": 0,
133
+ "refused_model": 0,
134
+ "provider_errors": 0,
135
+ "model": None,
136
+ }
137
+
138
+ def broker(messages):
139
+ state["requests"] += 1
140
+ if not policy.enabled:
141
+ state["refused_disabled"] += 1
142
+ return {"ok": False, "error":
143
+ "LLM access not granted: this agent did not declare llm_access.enabled"}
144
+
145
+ state["calls"] += 1
146
+ if state["calls"] > policy.max_calls:
147
+ state["refused_limit"] += 1
148
+ return {"ok": False, "error": "LLM call limit reached for this run"}
149
+
150
+ if _input_chars(messages) > policy.max_input_chars:
151
+ state["refused_input"] += 1
152
+ return {"ok": False, "error": "LLM request exceeds the allowed input size"}
153
+
154
+ try:
155
+ if policy.allowed_models is not None:
156
+ completion = base_broker(messages, allowed_models=policy.allowed_models)
157
+ else:
158
+ completion = base_broker(messages)
159
+ except LlmModelNotAllowedError as exc:
160
+ # Generic message to the agent; the host-side model name goes only
161
+ # into the usage counters (for audit), never into the sandbox.
162
+ state["refused_model"] += 1
163
+ state["model"] = getattr(exc, "model", "") or None
164
+ return {"ok": False, "error": str(exc)}
165
+ except LlmBrokerError as exc:
166
+ # LlmBrokerError is our own already-sanitized type (no key/internals).
167
+ state["provider_errors"] += 1
168
+ return {"ok": False, "error": str(exc)}
169
+ except Exception:
170
+ # Never surface a raw provider/host exception (may carry secrets).
171
+ state["provider_errors"] += 1
172
+ return {"ok": False, "error": "LLM call failed"}
173
+
174
+ content = str(completion.get("content", "") or "") if isinstance(completion, dict) else ""
175
+ if len(content) > policy.max_output_chars:
176
+ state["refused_output"] += 1
177
+ return {"ok": False, "error": "LLM response exceeds the allowed output size"}
178
+
179
+ state["ok"] += 1
180
+ return {"ok": True, "completion": completion}
181
+
182
+ broker.usage = state
183
+ broker.policy = policy
184
+ return broker
@@ -1252,6 +1252,13 @@ def run_agent(
1252
1252
  # or community code runs unsandboxed (reintroducing the RCE class the sandbox
1253
1253
  # bow closed). Locked by test_agent_runner's execution-vector regression test.
1254
1254
  trust_level = entry.get("trust_level", "unverified")
1255
+ # B2a: with the agent-sandbox flag ON, community (verified/unverified) agents
1256
+ # run sandboxed-or-fail-closed (never host) instead of being refused. Flag OFF
1257
+ # (default) ⇒ this branch is skipped ⇒ behaviour is unchanged (the gate below
1258
+ # refuses them). trusted/curated always fall through to the host path.
1259
+ from agentnode_sdk.runtimes.agent_sandbox import _agent_sandbox_enabled, run_agent_sandboxed
1260
+ if _agent_sandbox_enabled() and trust_level in ("verified", "unverified"):
1261
+ return run_agent_sandboxed(slug, entry, agent_config, goal=goal, run_id=run_id, **kwargs)
1255
1262
  if not _trust_meets_minimum(trust_level, "trusted"):
1256
1263
  _audit_agent_run(
1257
1264
  slug, success=False,