agentnode-sdk 0.11.4__tar.gz → 0.12.1__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.4 → agentnode_sdk-0.12.1}/CHANGELOG.md +82 -0
  2. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/PKG-INFO +1 -1
  3. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/__init__.py +1 -1
  4. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/cli/templates.py +15 -0
  5. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/config.py +14 -1
  6. agentnode_sdk-0.12.1/agentnode_sdk/runtimes/agent_llm_broker.py +83 -0
  7. agentnode_sdk-0.12.1/agentnode_sdk/runtimes/agent_llm_policy.py +184 -0
  8. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/runtimes/agent_runner.py +7 -0
  9. agentnode_sdk-0.12.1/agentnode_sdk/runtimes/agent_sandbox.py +218 -0
  10. agentnode_sdk-0.12.1/agentnode_sdk/sandbox/agent_container_wrapper.py +83 -0
  11. agentnode_sdk-0.12.1/agentnode_sdk/sandbox/agent_rpc.py +156 -0
  12. agentnode_sdk-0.12.1/agentnode_sdk/sandbox/agent_session.py +68 -0
  13. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/sandbox/backend.py +16 -0
  14. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/sandbox/container_backend.py +81 -1
  15. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/pyproject.toml +1 -1
  16. agentnode_sdk-0.12.1/spikes/agent_sandbox_routing/README.md +111 -0
  17. agentnode_sdk-0.12.1/spikes/agent_sandbox_routing/__init__.py +9 -0
  18. agentnode_sdk-0.12.1/spikes/agent_sandbox_routing/container_agent_wrapper.py +73 -0
  19. agentnode_sdk-0.12.1/spikes/agent_sandbox_routing/fake_llm.py +14 -0
  20. agentnode_sdk-0.12.1/spikes/agent_sandbox_routing/host_driver.py +145 -0
  21. agentnode_sdk-0.12.1/spikes/agent_sandbox_routing/trivial_agent.py +38 -0
  22. agentnode_sdk-0.12.1/tests/test_agent_llm_broker.py +179 -0
  23. agentnode_sdk-0.12.1/tests/test_agent_llm_policy.py +279 -0
  24. agentnode_sdk-0.12.1/tests/test_agent_rpc.py +186 -0
  25. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_agent_runner.py +84 -0
  26. agentnode_sdk-0.12.1/tests/test_agent_sandbox_e2e.py +175 -0
  27. agentnode_sdk-0.12.1/tests/test_agent_sandbox_routing.py +420 -0
  28. agentnode_sdk-0.12.1/tests/test_agent_sandbox_spike.py +87 -0
  29. agentnode_sdk-0.12.1/tests/test_agent_session_container.py +173 -0
  30. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_config.py +99 -0
  31. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/.env.example +0 -0
  32. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/.gitignore +0 -0
  33. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/README.md +0 -0
  34. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/REGISTRY_SIGNING_ACTIVATION.md +0 -0
  35. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/REGISTRY_SIGNING_SPEC.md +0 -0
  36. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/THREAT_MODEL.md +0 -0
  37. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/TRUST_STACK.md +0 -0
  38. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode.lock +0 -0
  39. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/_fileutil.py +0 -0
  40. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/async_client.py +0 -0
  41. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/capability_graph.py +0 -0
  42. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/capability_taxonomy.py +0 -0
  43. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/cli/__init__.py +0 -0
  44. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/cli/__main__.py +0 -0
  45. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/cli/audit.py +0 -0
  46. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/cli/auth.py +0 -0
  47. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/cli/cassette_audit.py +0 -0
  48. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/cli/commands.py +0 -0
  49. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/cli/complements.py +0 -0
  50. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/cli/init.py +0 -0
  51. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/cli/main.py +0 -0
  52. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/cli/mcp_commands.py +0 -0
  53. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/cli/mcp_status.py +0 -0
  54. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/cli/mcp_submit.py +0 -0
  55. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/cli/mcp_verify.py +0 -0
  56. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/cli/output.py +0 -0
  57. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/cli/publish.py +0 -0
  58. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/cli/record_cases.py +0 -0
  59. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/cli/sandbox_commands.py +0 -0
  60. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/cli/serve.py +0 -0
  61. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/cli/setup_wizard.py +0 -0
  62. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/cli/smart_run.py +0 -0
  63. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/cli/validate.py +0 -0
  64. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/cli/verify_local.py +0 -0
  65. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/client.py +0 -0
  66. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/compatibility.py +0 -0
  67. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/credential_handle.py +0 -0
  68. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/credential_resolver.py +0 -0
  69. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/credential_store.py +0 -0
  70. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/detect.py +0 -0
  71. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/exceptions.py +0 -0
  72. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/guard.py +0 -0
  73. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/input_guard.py +0 -0
  74. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/installer.py +0 -0
  75. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/key_status.py +0 -0
  76. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/lock_integrity.py +0 -0
  77. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/mcp_server.py +0 -0
  78. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/models.py +0 -0
  79. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/planner.py +0 -0
  80. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/policy.py +0 -0
  81. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/references.py +0 -0
  82. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/registry_trust.py +0 -0
  83. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/resolve.py +0 -0
  84. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/resource_provider.py +0 -0
  85. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/risk_profile.py +0 -0
  86. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/run_log.py +0 -0
  87. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/runner.py +0 -0
  88. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/runtime.py +0 -0
  89. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/runtimes/__init__.py +0 -0
  90. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/runtimes/mcp_runner.py +0 -0
  91. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/runtimes/python_runner.py +0 -0
  92. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/runtimes/remote_runner.py +0 -0
  93. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/sandbox/__init__.py +0 -0
  94. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/sandbox/policy.py +0 -0
  95. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/sandbox/types.py +0 -0
  96. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/signature.py +0 -0
  97. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/signing_key.py +0 -0
  98. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/agentnode_sdk/skill.py +0 -0
  99. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/sandbox-image/Dockerfile +0 -0
  100. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/sandbox-image/README.md +0 -0
  101. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/scripts/analyze_scores.py +0 -0
  102. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/scripts/batch_verify.py +0 -0
  103. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/scripts/ci_smoke_test.py +0 -0
  104. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/scripts/generate_compatibility_artifacts.py +0 -0
  105. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/scripts/verify_toolcalls.py +0 -0
  106. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/scripts/weekly_retest.sh +0 -0
  107. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/__init__.py +0 -0
  108. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/conftest.py +0 -0
  109. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_async_client.py +0 -0
  110. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_audit_ux.py +0 -0
  111. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_auto_upgrade_policy.py +0 -0
  112. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_cli.py +0 -0
  113. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_cli_lock.py +0 -0
  114. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_cli_run_resolution.py +0 -0
  115. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_client.py +0 -0
  116. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_client_json_guard.py +0 -0
  117. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_client_sprint_b.py +0 -0
  118. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_credential_handle.py +0 -0
  119. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_credential_integration.py +0 -0
  120. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_credential_resolver.py +0 -0
  121. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_credential_store.py +0 -0
  122. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_detect.py +0 -0
  123. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_detect_and_install.py +0 -0
  124. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_e2e_runtime.py +0 -0
  125. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_edge_cases.py +0 -0
  126. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_guard.py +0 -0
  127. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_guard_check.py +0 -0
  128. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_guard_config_cache.py +0 -0
  129. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_guard_policy.py +0 -0
  130. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_guard_preview.py +0 -0
  131. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_guard_schema.py +0 -0
  132. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_guard_set.py +0 -0
  133. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_guard_status.py +0 -0
  134. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_guard_tool_override.py +0 -0
  135. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_guard_tool_override_audit.py +0 -0
  136. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_guard_tool_override_cli.py +0 -0
  137. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_guard_ux.py +0 -0
  138. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_input_guard.py +0 -0
  139. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_input_guard_escalation.py +0 -0
  140. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_install_hardening.py +0 -0
  141. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_installer_sprint_b.py +0 -0
  142. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_intelligence.py +0 -0
  143. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_key_status.py +0 -0
  144. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_llm_binding.py +0 -0
  145. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_llm_call_runlog.py +0 -0
  146. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_lock_integrity.py +0 -0
  147. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_lock_runtime.py +0 -0
  148. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_mcp_audit.py +0 -0
  149. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_mcp_doctor.py +0 -0
  150. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_mcp_sandbox.py +0 -0
  151. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_mcp_server.py +0 -0
  152. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_observability.py +0 -0
  153. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_planner.py +0 -0
  154. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_policy.py +0 -0
  155. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_policy_integration.py +0 -0
  156. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_prompt_specs.py +0 -0
  157. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_provider_matrix.py +0 -0
  158. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_publish.py +0 -0
  159. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_references.py +0 -0
  160. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_registry_trust.py +0 -0
  161. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_remote_hardening.py +0 -0
  162. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_remote_runner.py +0 -0
  163. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_resource_provider.py +0 -0
  164. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_resource_specs.py +0 -0
  165. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_risk_profile.py +0 -0
  166. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_run_log.py +0 -0
  167. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_runner.py +0 -0
  168. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_runtime.py +0 -0
  169. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_runtime_audit.py +0 -0
  170. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_sandbox_backend.py +0 -0
  171. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_sandbox_doctor.py +0 -0
  172. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_sandbox_e2e.py +0 -0
  173. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_sandbox_gate.py +0 -0
  174. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_security_hardening.py +0 -0
  175. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_signature.py +0 -0
  176. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_signing_key.py +0 -0
  177. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_skill.py +0 -0
  178. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_smart.py +0 -0
  179. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_stability.py +0 -0
  180. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_toolpack_sandbox.py +0 -0
  181. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_v02.py +0 -0
  182. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_validate.py +0 -0
  183. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/test_validate_skill.py +0 -0
  184. {agentnode_sdk-0.11.4 → agentnode_sdk-0.12.1}/tests/validation_lockfile.json +0 -0
@@ -1,5 +1,87 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.12.1 — agent_sandbox config fix
4
+
5
+ ### Fixed
6
+
7
+ - **`agent_sandbox` config section was stripped during `load_config()`.** The
8
+ config loader rebuilt the file from defaults and silently dropped a
9
+ hand-written `agent_sandbox` section, so the documented
10
+ `agent_sandbox.enabled: true` config path never took effect (only the
11
+ `AGENTNODE_AGENT_SANDBOX` env var worked), and the host LLM ceiling
12
+ (`agent_sandbox.llm.*`) never reached policy resolution. Both now survive
13
+ loading; the nested `llm` ceiling is passed through verbatim.
14
+ - **`agentnode config set agent_sandbox.enabled true|false` now works** (the
15
+ key was missing from the allowed-keys whitelist) and stores a **real
16
+ boolean** — previously a stored string `"false"` would have been truthy,
17
+ i.e. read as enabled.
18
+
19
+ ### Upgrade Notes
20
+
21
+ - No breaking changes. No behavior change unless you use the `agent_sandbox`
22
+ config path; the env var `AGENTNODE_AGENT_SANDBOX` behaves exactly as
23
+ before. The agent sandbox remains **default OFF**.
24
+
25
+ ## 0.12.0 — Sandboxed community agents (flag-gated)
26
+
27
+ ### Added
28
+
29
+ - **Agent sandbox (default OFF):** with `AGENTNODE_AGENT_SANDBOX=1` (or config
30
+ `agent_sandbox.enabled: true`), `verified`/`unverified` community agents run
31
+ **sandbox-or-fail-closed** in the pinned container image — never on the host,
32
+ with **no host fallback** anywhere on the path. Tool calls cross an
33
+ allowlisted RPC back to the host's gated runner (the host owns allowlist and
34
+ limits); `trusted`/`curated` agents are unchanged. With the flag OFF
35
+ (default), community agents remain refused exactly as in 0.11.4.
36
+ - **Host-side LLM broker:** sandboxed agents request completions via RPC; the
37
+ provider call runs host-side and **provider API keys never enter the
38
+ container** (the container env is only `PYTHONPATH=/pack`).
39
+ - **`llm_access` manifest block (default-deny):** a sandboxed agent gets NO
40
+ host LLM unless its manifest declares `llm_access.enabled: true` — analogous
41
+ to `tool_access`. Caps: `max_calls`, `max_input_chars`, `max_output_chars`;
42
+ optional `allowed_models` checks the HOST-chosen model (the agent never picks
43
+ a model; absent = unrestricted, `[]` = refuse-all, manifest+host = both must
44
+ allow). The host ceiling (`agent_sandbox.llm` in `~/.agentnode/config.json`)
45
+ always wins — it can lower caps, restrict models, or disable access entirely.
46
+ Refused/failed LLM calls return **graceful per-call errors** the agent can
47
+ catch; they never crash the run and never fall back to the host.
48
+ - **Audit:** every sandboxed run writes ONE aggregated, sanitized record to
49
+ `~/.agentnode/audit.jsonl` (`event: agent_run`, `source: agent_sandbox`) —
50
+ counters, caps, and fixed reason codes only; **never prompts, keys, raw
51
+ provider errors, or agent-authored error text**. Fail-closed refusals
52
+ (missing volume/runtime, session-start failure) are audited too.
53
+ - Agent manifest template (`agentnode init`) now documents the opt-in
54
+ `llm_access` block with the caps and `allowed_models`.
55
+
56
+ ### Changed
57
+
58
+ - `agentnode init` agent template includes the `llm_access` example (newly
59
+ scaffolded packages only; existing manifests are unaffected — an absent
60
+ `llm_access` simply means deny).
61
+
62
+ ### Hardened
63
+
64
+ - The sandbox path is fail-closed end to end: missing/stale volume, missing
65
+ container runtime or pinned image, sandbox-start failure, or a host-loop
66
+ error all return a clean `sandbox_unavailable`/error result — community
67
+ agent code never executes on the host.
68
+ - LLM broker errors are generic and leak-free (no key, no provider internals,
69
+ no prompt echo). A model-allowlist refusal never calls the provider (no
70
+ charge), and the host-side model name is never sent into the sandbox.
71
+
72
+ ### BREAKING / Upgrade Notes
73
+
74
+ - **None.** With the flag OFF (default), behavior is identical to 0.11.4.
75
+ There are no flag-ON users yet (the flag ships first in this release).
76
+ - Enabling the agent sandbox requires a container runtime plus the pinned
77
+ public sandbox image — `agentnode sandbox pull` to fetch it,
78
+ `agentnode sandbox doctor` for diagnosis.
79
+ - Operational note for managed hosts (e.g. Coolify): automatic image pruning
80
+ can remove the pinned sandbox image, degrading community execution to
81
+ fail-closed until it is re-pulled. Keep the image pinned (e.g. a minimal
82
+ keep-alive holder container referencing the digest) or re-pull on a
83
+ schedule.
84
+
3
85
  ## 0.11.4 — Publish confirm gate
4
86
 
5
87
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentnode-sdk
3
- Version: 0.11.4
3
+ Version: 0.12.1
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.4"
41
+ __version__ = "0.12.1"
42
42
  __all__ = [
43
43
  "AgentNode",
44
44
  "AsyncAgentNode",
@@ -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.
@@ -41,6 +41,9 @@ DEFAULTS: dict[str, Any] = {
41
41
  "compute": "allow",
42
42
  "unknown": "prompt",
43
43
  },
44
+ "agent_sandbox": {
45
+ "enabled": False,
46
+ },
44
47
  }
45
48
 
46
49
  VALID_VALUES: dict[str, tuple[str, ...]] = {
@@ -61,6 +64,7 @@ VALID_VALUES: dict[str, tuple[str, ...]] = {
61
64
  "guard.read": ("allow", "prompt", "deny"),
62
65
  "guard.compute": ("allow", "prompt", "deny"),
63
66
  "guard.unknown": ("allow", "prompt", "deny"),
67
+ "agent_sandbox.enabled": ("true", "false"),
64
68
  }
65
69
 
66
70
 
@@ -82,6 +86,7 @@ CONFIG_DESCRIPTIONS: dict[str, str] = {
82
86
  "guard.read": "Guard policy for tools that read data",
83
87
  "guard.compute": "Guard policy for tools that perform computation",
84
88
  "guard.unknown": "Guard policy for tools with unknown action type",
89
+ "agent_sandbox.enabled": "Route community (verified/unverified) agents through the container sandbox",
85
90
  }
86
91
 
87
92
 
@@ -138,6 +143,14 @@ def _merge_defaults(data: dict) -> dict[str, Any]:
138
143
  for extra_key in ("rate_limits", "agent_overrides", "tool_overrides"):
139
144
  if extra_key in data["guard"]:
140
145
  cfg["guard"][extra_key] = data["guard"][extra_key]
146
+ if isinstance(data.get("agent_sandbox"), dict):
147
+ if "enabled" in data["agent_sandbox"]:
148
+ cfg["agent_sandbox"]["enabled"] = data["agent_sandbox"]["enabled"]
149
+ # The nested llm ceiling (max_calls/max_input_chars/max_output_chars/
150
+ # allowed_models/enabled) holds ints/lists — pass it through verbatim,
151
+ # like the guard extra keys above.
152
+ if isinstance(data["agent_sandbox"].get("llm"), dict):
153
+ cfg["agent_sandbox"]["llm"] = data["agent_sandbox"]["llm"]
141
154
  return cfg
142
155
 
143
156
 
@@ -201,7 +214,7 @@ def set_value(cfg: dict[str, Any], key: str, value: str) -> dict[str, Any]:
201
214
  f"Allowed: {', '.join(allowed)}"
202
215
  )
203
216
 
204
- bool_keys = ("credentials.require_before_auto_install",)
217
+ bool_keys = ("credentials.require_before_auto_install", "agent_sandbox.enabled")
205
218
  actual_value: Any = value.lower() == "true" if key in bool_keys else value.lower()
206
219
 
207
220
  parts = key.split(".")
@@ -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,