b3-prosaic-runtime 0.9.1__tar.gz → 0.9.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/CHANGELOG.md +8 -0
  2. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/PKG-INFO +43 -1
  3. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/README.md +42 -0
  4. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/pyproject.toml +1 -1
  5. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/scripts/conversation_wheel_smoke.py +1 -1
  6. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/scripts/production_wheel_smoke.py +1 -1
  7. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/b3_prosaic_runtime.egg-info/PKG-INFO +43 -1
  8. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/b3_prosaic_runtime.egg-info/SOURCES.txt +2 -0
  9. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/__init__.py +4 -1
  10. b3_prosaic_runtime-0.9.2/src/prosaic_runtime/execution_budget.py +108 -0
  11. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/operation_context.py +23 -1
  12. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/runtime.py +33 -9
  13. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/tools.py +2 -2
  14. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_conformance.py +1 -1
  15. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_distribution_metadata.py +1 -1
  16. b3_prosaic_runtime-0.9.2/tests/test_execution_budget.py +128 -0
  17. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_package_compatibility.py +2 -2
  18. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/LICENSE +0 -0
  19. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/LICENSE-MIT +0 -0
  20. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/MANIFEST.in +0 -0
  21. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/NOTICE.md +0 -0
  22. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/docs/accounting.md +0 -0
  23. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/docs/adr-cli-sandbox.md +0 -0
  24. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/docs/cli-tools.md +0 -0
  25. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/docs/conformance.md +0 -0
  26. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/docs/conversations.md +0 -0
  27. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/docs/package-index.md +0 -0
  28. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/docs/production-acceptance.md +0 -0
  29. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/docs/release-0.7.1.md +0 -0
  30. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/docs/security.md +0 -0
  31. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/docs/structured-tools.md +0 -0
  32. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/docs/telemetry.md +0 -0
  33. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/docs/tool-effects.md +0 -0
  34. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/docs/workflow-factory-runtime-implementation.md +0 -0
  35. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/.prosaic/subagents/acquire-pilot.md +0 -0
  36. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/.prosaic/subagents/acquired-reviewer.md +0 -0
  37. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/.prosaic/subagents/catalog-reader.md +0 -0
  38. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/.prosaic/subagents/cli-spec-reviewer.md +0 -0
  39. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/.prosaic/subagents/launch-analyst.md +0 -0
  40. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/.prosaic/subagents/launch-briefer.md +0 -0
  41. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/.prosaic/subagents/launch-decision.md +0 -0
  42. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/.prosaic/subagents/launch-skeptic.md +0 -0
  43. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/.prosaic/subagents/native-summarizer.md +0 -0
  44. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/.prosaic/subagents/preloaded-reviewer.md +0 -0
  45. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/.prosaic/subagents/reviewer.md +0 -0
  46. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/.prosaic/subagents/summarizer.md +0 -0
  47. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/.prosaic/tools/analyze-spec.yml +0 -0
  48. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/README.md +0 -0
  49. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/anthropic.yml +0 -0
  50. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/catalog.json +0 -0
  51. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/catalog_tools.py +0 -0
  52. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/cli-tool/prosaic_example_analyzer.py +0 -0
  53. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/cli-tool/pyproject.toml +0 -0
  54. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/cli-tools-sandboxed.yml +0 -0
  55. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/cli-tools.yml +0 -0
  56. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/custom-tools.yml +0 -0
  57. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/evidence/launch/brief.md +0 -0
  58. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/evidence/launch/field-notes.md +0 -0
  59. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/evidence/launch/metrics.md +0 -0
  60. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/evidence/pilot.md +0 -0
  61. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/evidence/requirements.md +0 -0
  62. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/prosaic-runtime.yaml +0 -0
  63. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/run_acquisition.py +0 -0
  64. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/run_cli_tool.py +0 -0
  65. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/run_custom_tool.py +0 -0
  66. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/run_examples.py +0 -0
  67. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/run_sandbox_probe.py +0 -0
  68. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/run_tiers.py +0 -0
  69. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/tokenproxy.yml +0 -0
  70. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/understanding/.prosaic/subagents/reviewer.md +0 -0
  71. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/understanding/.prosaic/tools/understanding.yml +0 -0
  72. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/understanding/runtime.yml +0 -0
  73. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/with-tools.yml +0 -0
  74. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/scripts/release_artifacts.py +0 -0
  75. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/scripts/wheel_smoke.py +0 -0
  76. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/setup.cfg +0 -0
  77. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/b3_prosaic_runtime.egg-info/dependency_links.txt +0 -0
  78. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/b3_prosaic_runtime.egg-info/entry_points.txt +0 -0
  79. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/b3_prosaic_runtime.egg-info/requires.txt +0 -0
  80. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/b3_prosaic_runtime.egg-info/top_level.txt +0 -0
  81. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/accounting.py +0 -0
  82. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/accounting_capture.py +0 -0
  83. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/adapter.py +0 -0
  84. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/admission.py +0 -0
  85. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/anthropic.py +0 -0
  86. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/anthropic_stream.py +0 -0
  87. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/anthropic_usage.py +0 -0
  88. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/artifacts.py +0 -0
  89. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/budgets.py +0 -0
  90. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/cli.py +0 -0
  91. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/cli_tools.py +0 -0
  92. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/config.py +0 -0
  93. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/conformance.py +0 -0
  94. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/console.py +0 -0
  95. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/conversation.py +0 -0
  96. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/conversation_preparation.py +0 -0
  97. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/diagnostics.py +0 -0
  98. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/events.py +0 -0
  99. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/execution.py +0 -0
  100. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/http_bounds.py +0 -0
  101. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/openai_compatible.py +0 -0
  102. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/openai_compatible_compaction.py +0 -0
  103. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/openai_compatible_filters.py +0 -0
  104. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/openai_compatible_progress.py +0 -0
  105. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/openai_compatible_transcript.py +0 -0
  106. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/policy.py +0 -0
  107. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/provider_types.py +0 -0
  108. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/sandbox.py +0 -0
  109. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/smoke_prose/pilot.md +0 -0
  110. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/smoke_prose/subagents/native-summarizer.md +0 -0
  111. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/smoke_prose/subagents/reviewer.md +0 -0
  112. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/smoke_prose/subagents/summarizer.md +0 -0
  113. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/structured_tools.py +0 -0
  114. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/telemetry.py +0 -0
  115. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/tool_effects.py +0 -0
  116. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/tool_registry.py +0 -0
  117. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/turn_capture.py +0 -0
  118. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/types.py +0 -0
  119. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/support/conversation_server.py +0 -0
  120. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/support/tool_journal.py +0 -0
  121. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_accounting.py +0 -0
  122. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_acquisition.py +0 -0
  123. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_anthropic.py +0 -0
  124. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_anthropic_accounting.py +0 -0
  125. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_anthropic_package.py +0 -0
  126. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_anthropic_stream.py +0 -0
  127. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_artifact_admission.py +0 -0
  128. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_cli_sandbox.py +0 -0
  129. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_cli_tool_example.py +0 -0
  130. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_cli_tools.py +0 -0
  131. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_cli_tools_boundaries.py +0 -0
  132. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_config.py +0 -0
  133. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_conversation_bounds.py +0 -0
  134. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_conversation_contracts.py +0 -0
  135. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_conversation_distribution.py +0 -0
  136. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_conversation_failure_evidence.py +0 -0
  137. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_conversation_review_regressions.py +0 -0
  138. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_conversation_tool_batches.py +0 -0
  139. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_conversation_turns.py +0 -0
  140. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_custom_tool_example.py +0 -0
  141. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_custom_tool_transport.py +0 -0
  142. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_custom_tools.py +0 -0
  143. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_features.py +0 -0
  144. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_http_boundaries.py +0 -0
  145. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_input_boundary.py +0 -0
  146. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_invocation_budgets.py +0 -0
  147. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_production_smoke.py +0 -0
  148. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_prompt_injection.py +0 -0
  149. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_provider_execution.py +0 -0
  150. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_read_provenance.py +0 -0
  151. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_redirect_safety.py +0 -0
  152. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_release_artifacts.py +0 -0
  153. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_run_adapter.py +0 -0
  154. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_runtime.py +0 -0
  155. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_sample_program.py +0 -0
  156. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_sandbox_probe_example.py +0 -0
  157. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_structured_tools.py +0 -0
  158. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_telemetry.py +0 -0
  159. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_tier_examples.py +0 -0
  160. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_tool_effect_workers.py +0 -0
  161. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_tool_effects.py +0 -0
  162. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_transport.py +0 -0
  163. {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_usage_integrity.py +0 -0
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.2 — 2026-10-13
4
+
5
+ - Add a host-owned ExecutionBudget that shares deadlines, provider/tool dispatch
6
+ allowances and known/unknown reported-token evidence across nested calls/retries.
7
+ - Preserve trusted parent accounting context in tool callbacks and expose bounded
8
+ child lineage and remaining-time helpers without placing attribution in prompts.
9
+ - Qualify SDK and existing PostgreSQL recorder compatibility; publish only the SDK.
10
+
3
11
  ## 0.9.1 — 2026-10-13
4
12
 
5
13
  - Add versioned `RuntimeAdapter` for trusted host projections and normalization
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: b3-prosaic-runtime
3
- Version: 0.9.1
3
+ Version: 0.9.2
4
4
  Summary: Bounded execution of neutral Prosaic agents on OpenAI-compatible and native Anthropic endpoints
5
5
  Author: B3 Cognition
6
6
  License-Expression: Apache-2.0
@@ -653,3 +653,45 @@ For endpoints where the host deliberately chooses JSON tool requests, use the
653
653
  shared [`StructuredToolLoop`](docs/structured-tools.md). It reuses registered
654
654
  `CustomTool` contracts and bounded execution, with application-owned final-result
655
655
  admission. Harness can drive each turn without embedding product rules in Runtime.
656
+
657
+ ### Shared budgets for nested host execution (Runtime 0.9.2)
658
+
659
+ Use `ExecutionBudget` around a complete host operation to share one absolute
660
+ monotonic deadline, provider/tool dispatch caps and an optional reported-token cap
661
+ across `run()`, `turn()`, RuntimeAdapter retries and synchronous nested callbacks:
662
+
663
+ ```python
664
+ from prosaic_runtime import ExecutionBudget
665
+
666
+ budget = ExecutionBudget(timeout_s=30, max_provider_requests=50,
667
+ max_tool_calls=1, max_reported_tokens=40960,
668
+ cancelled=request_cancelled)
669
+ with budget.activate():
670
+ reply = conversation.send(...)
671
+ ```
672
+
673
+ Reactivation never replenishes counters or the deadline. Nested budget scopes
674
+ must satisfy every enclosing allowance. Provider/tool attempts are reserved
675
+ atomically; explicit worker threads must activate the same budget object because
676
+ context variables are not automatically inherited by threads. This cooperative
677
+ host contract does not sandbox or preempt trusted callbacks. Callback networking
678
+ must narrow its own timeout to the supplied deadline and check cancellation.
679
+
680
+ `ToolExecutionContext.accounting_context` holds the actual frozen resolved parent
681
+ attribution when accounting is enabled. `child_context(invocation_id=..., run_id=...,
682
+ step_id=...)` derives nested lineage without taking identities from model arguments.
683
+ `remaining_timeout(maximum)` narrows a callback timeout to the original deadline.
684
+ Attribution is private host context and must not be copied into prompts/results.
685
+
686
+ Known reported tokens remain available through `budget.reported_tokens`; unknown
687
+ usage remains unknown through `budget.usage_complete`. A configured token cap
688
+ stops subsequent dispatch on unknown usage. A response can itself exceed a
689
+ reported-token cap; its paid evidence is retained and the operation fails rather
690
+ than pretending the provider call was prevented. This is not a currency cap.
691
+
692
+ ALWAYS persist host operation identity and conservative remaining reservations
693
+ before dispatch when crash recovery matters. NEVER treat this process-local
694
+ budget as a durable ledger or reset uncertain allowances after a restart.
695
+ PostgreSQL accounting remains the authoritative provider-call evidence. Saved
696
+ host outcomes require a fenced ToolJournal or equivalent durable application
697
+ repository; interrupted effects must not be repeated automatically.
@@ -631,3 +631,45 @@ For endpoints where the host deliberately chooses JSON tool requests, use the
631
631
  shared [`StructuredToolLoop`](docs/structured-tools.md). It reuses registered
632
632
  `CustomTool` contracts and bounded execution, with application-owned final-result
633
633
  admission. Harness can drive each turn without embedding product rules in Runtime.
634
+
635
+ ### Shared budgets for nested host execution (Runtime 0.9.2)
636
+
637
+ Use `ExecutionBudget` around a complete host operation to share one absolute
638
+ monotonic deadline, provider/tool dispatch caps and an optional reported-token cap
639
+ across `run()`, `turn()`, RuntimeAdapter retries and synchronous nested callbacks:
640
+
641
+ ```python
642
+ from prosaic_runtime import ExecutionBudget
643
+
644
+ budget = ExecutionBudget(timeout_s=30, max_provider_requests=50,
645
+ max_tool_calls=1, max_reported_tokens=40960,
646
+ cancelled=request_cancelled)
647
+ with budget.activate():
648
+ reply = conversation.send(...)
649
+ ```
650
+
651
+ Reactivation never replenishes counters or the deadline. Nested budget scopes
652
+ must satisfy every enclosing allowance. Provider/tool attempts are reserved
653
+ atomically; explicit worker threads must activate the same budget object because
654
+ context variables are not automatically inherited by threads. This cooperative
655
+ host contract does not sandbox or preempt trusted callbacks. Callback networking
656
+ must narrow its own timeout to the supplied deadline and check cancellation.
657
+
658
+ `ToolExecutionContext.accounting_context` holds the actual frozen resolved parent
659
+ attribution when accounting is enabled. `child_context(invocation_id=..., run_id=...,
660
+ step_id=...)` derives nested lineage without taking identities from model arguments.
661
+ `remaining_timeout(maximum)` narrows a callback timeout to the original deadline.
662
+ Attribution is private host context and must not be copied into prompts/results.
663
+
664
+ Known reported tokens remain available through `budget.reported_tokens`; unknown
665
+ usage remains unknown through `budget.usage_complete`. A configured token cap
666
+ stops subsequent dispatch on unknown usage. A response can itself exceed a
667
+ reported-token cap; its paid evidence is retained and the operation fails rather
668
+ than pretending the provider call was prevented. This is not a currency cap.
669
+
670
+ ALWAYS persist host operation identity and conservative remaining reservations
671
+ before dispatch when crash recovery matters. NEVER treat this process-local
672
+ budget as a durable ledger or reset uncertain allowances after a restart.
673
+ PostgreSQL accounting remains the authoritative provider-call evidence. Saved
674
+ host outcomes require a fenced ToolJournal or equivalent durable application
675
+ repository; interrupted effects must not be repeated automatically.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "b3-prosaic-runtime"
7
- version = "0.9.1"
7
+ version = "0.9.2"
8
8
  description = "Bounded execution of neutral Prosaic agents on OpenAI-compatible and native Anthropic endpoints"
9
9
  requires-python = ">=3.11"
10
10
  readme = "README.md"
@@ -22,7 +22,7 @@ def ownership():
22
22
  import prosaic_runtime as api
23
23
  import prosaic_runtime_postgres as recorder
24
24
  for module, distribution, version in ((prosaic, 'b3-prosaic', '0.4.1'),
25
- (api, 'b3-prosaic-runtime', '0.9.1'),
25
+ (api, 'b3-prosaic-runtime', '0.9.2'),
26
26
  (recorder, 'b3-prosaic-runtime-postgres', '0.3.0')):
27
27
  owner = metadata.distribution(distribution)
28
28
  assert module.__version__ == owner.version == version
@@ -49,7 +49,7 @@ def installed_origins():
49
49
  owners = {}
50
50
  origins = {}
51
51
  for package, name, version in ((prosaic, "b3-prosaic", "0.4.1"),
52
- (runtime, "b3-prosaic-runtime", "0.9.1"),
52
+ (runtime, "b3-prosaic-runtime", "0.9.2"),
53
53
  (postgres, "b3-prosaic-runtime-postgres", "0.3.0")):
54
54
  owner = metadata.distribution(name)
55
55
  assert owner.version == package.__version__ == version
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: b3-prosaic-runtime
3
- Version: 0.9.1
3
+ Version: 0.9.2
4
4
  Summary: Bounded execution of neutral Prosaic agents on OpenAI-compatible and native Anthropic endpoints
5
5
  Author: B3 Cognition
6
6
  License-Expression: Apache-2.0
@@ -653,3 +653,45 @@ For endpoints where the host deliberately chooses JSON tool requests, use the
653
653
  shared [`StructuredToolLoop`](docs/structured-tools.md). It reuses registered
654
654
  `CustomTool` contracts and bounded execution, with application-owned final-result
655
655
  admission. Harness can drive each turn without embedding product rules in Runtime.
656
+
657
+ ### Shared budgets for nested host execution (Runtime 0.9.2)
658
+
659
+ Use `ExecutionBudget` around a complete host operation to share one absolute
660
+ monotonic deadline, provider/tool dispatch caps and an optional reported-token cap
661
+ across `run()`, `turn()`, RuntimeAdapter retries and synchronous nested callbacks:
662
+
663
+ ```python
664
+ from prosaic_runtime import ExecutionBudget
665
+
666
+ budget = ExecutionBudget(timeout_s=30, max_provider_requests=50,
667
+ max_tool_calls=1, max_reported_tokens=40960,
668
+ cancelled=request_cancelled)
669
+ with budget.activate():
670
+ reply = conversation.send(...)
671
+ ```
672
+
673
+ Reactivation never replenishes counters or the deadline. Nested budget scopes
674
+ must satisfy every enclosing allowance. Provider/tool attempts are reserved
675
+ atomically; explicit worker threads must activate the same budget object because
676
+ context variables are not automatically inherited by threads. This cooperative
677
+ host contract does not sandbox or preempt trusted callbacks. Callback networking
678
+ must narrow its own timeout to the supplied deadline and check cancellation.
679
+
680
+ `ToolExecutionContext.accounting_context` holds the actual frozen resolved parent
681
+ attribution when accounting is enabled. `child_context(invocation_id=..., run_id=...,
682
+ step_id=...)` derives nested lineage without taking identities from model arguments.
683
+ `remaining_timeout(maximum)` narrows a callback timeout to the original deadline.
684
+ Attribution is private host context and must not be copied into prompts/results.
685
+
686
+ Known reported tokens remain available through `budget.reported_tokens`; unknown
687
+ usage remains unknown through `budget.usage_complete`. A configured token cap
688
+ stops subsequent dispatch on unknown usage. A response can itself exceed a
689
+ reported-token cap; its paid evidence is retained and the operation fails rather
690
+ than pretending the provider call was prevented. This is not a currency cap.
691
+
692
+ ALWAYS persist host operation identity and conservative remaining reservations
693
+ before dispatch when crash recovery matters. NEVER treat this process-local
694
+ budget as a durable ledger or reset uncertain allowances after a restart.
695
+ PostgreSQL accounting remains the authoritative provider-call evidence. Saved
696
+ host outcomes require a fenced ToolJournal or equivalent durable application
697
+ repository; interrupted effects must not be repeated automatically.
@@ -87,6 +87,7 @@ src/prosaic_runtime/conversation_preparation.py
87
87
  src/prosaic_runtime/diagnostics.py
88
88
  src/prosaic_runtime/events.py
89
89
  src/prosaic_runtime/execution.py
90
+ src/prosaic_runtime/execution_budget.py
90
91
  src/prosaic_runtime/http_bounds.py
91
92
  src/prosaic_runtime/openai_compatible.py
92
93
  src/prosaic_runtime/openai_compatible_compaction.py
@@ -133,6 +134,7 @@ tests/test_custom_tool_example.py
133
134
  tests/test_custom_tool_transport.py
134
135
  tests/test_custom_tools.py
135
136
  tests/test_distribution_metadata.py
137
+ tests/test_execution_budget.py
136
138
  tests/test_features.py
137
139
  tests/test_http_boundaries.py
138
140
  tests/test_input_boundary.py
@@ -1,5 +1,5 @@
1
1
  """Execute neutral Prosaic artifacts with explicitly granted authority."""
2
- __version__ = '0.9.1'
2
+ __version__ = '0.9.2'
3
3
 
4
4
  from .artifacts import ProsaicArtifact
5
5
  from .config import EndpointConfig, RuntimeConfig, RunLimits, CliSandboxConfig
@@ -33,3 +33,6 @@ __all__ += ["ConversationMessage", "CompletedTurn", "TurnEvidence", "TurnResult"
33
33
 
34
34
  from .adapter import RuntimeAdapter
35
35
  __all__ += ["RuntimeAdapter"]
36
+
37
+ from .execution_budget import ExecutionBudget
38
+ __all__ += ["ExecutionBudget"]
@@ -0,0 +1,108 @@
1
+ """Host-owned, process-local allowance across nested synchronous Runtime calls."""
2
+ from contextlib import contextmanager
3
+ from contextvars import ContextVar
4
+ import math
5
+ from threading import RLock
6
+ import time
7
+
8
+ from .budgets import InvocationBudgetExceeded
9
+ from .events import Cancelled
10
+ from .tools import ToolDeadlineExceeded
11
+
12
+ _active = ContextVar('prosaic_execution_budgets', default=())
13
+
14
+
15
+ class ExecutionBudget:
16
+ """A fixed deadline and dispatch caps; activation never replenishes allowance.
17
+
18
+ This is admission, not a durable ledger or a sandbox for trusted callbacks.
19
+ Activate the same object explicitly in worker threads to share its counters.
20
+ """
21
+ def __init__(self, *, timeout_s, max_provider_requests, max_tool_calls, max_reported_tokens=None, cancelled=None):
22
+ if type(timeout_s) not in (int,float) or not math.isfinite(timeout_s) or timeout_s<=0:
23
+ raise ValueError('invalid execution timeout')
24
+ for cap in (max_provider_requests,max_tool_calls):
25
+ if type(cap) is not int or cap<0: raise ValueError('invalid execution allowance')
26
+ if max_reported_tokens is not None and (type(max_reported_tokens) is not int or max_reported_tokens<0):
27
+ raise ValueError('invalid reported token allowance')
28
+ if cancelled is not None and not callable(cancelled): raise ValueError('invalid cancellation callback')
29
+ self._deadline=time.monotonic()+timeout_s
30
+ self._provider_cap,self._tool_cap=max_provider_requests,max_tool_calls
31
+ self._provider_requests=self._tool_calls=self._reported_tokens=0
32
+ self._token_cap=max_reported_tokens
33
+ self._usage_complete=True
34
+ self._cancelled=cancelled
35
+ self._lock=RLock()
36
+
37
+ @property
38
+ def deadline(self): return self._deadline
39
+ @property
40
+ def provider_requests(self):
41
+ with self._lock: return self._provider_requests
42
+ @property
43
+ def tool_calls(self):
44
+ with self._lock: return self._tool_calls
45
+
46
+ @property
47
+ def reported_tokens(self):
48
+ with self._lock: return self._reported_tokens
49
+ @property
50
+ def usage_complete(self):
51
+ with self._lock: return self._usage_complete
52
+
53
+ def _check_tokens(self, *, dispatch=False):
54
+ if self._token_cap is not None:
55
+ if not self._usage_complete: raise InvocationBudgetExceeded('usage_unknown')
56
+ if self._reported_tokens>self._token_cap or (dispatch and self._reported_tokens>=self._token_cap):
57
+ raise InvocationBudgetExceeded('token_limit')
58
+
59
+ def check(self):
60
+ if self._cancelled is not None and self._cancelled(): raise Cancelled()
61
+ if time.monotonic()>=self._deadline: raise ToolDeadlineExceeded('execution deadline exceeded')
62
+
63
+ @contextmanager
64
+ def activate(self):
65
+ stack=_active.get()
66
+ token=_active.set(stack if self in stack else (*stack,self))
67
+ try: yield self
68
+ finally: _active.reset(token)
69
+
70
+
71
+ def active_budgets(): return _active.get()
72
+
73
+
74
+ def check_budgets(budgets):
75
+ for budget in budgets: budget.check()
76
+
77
+
78
+ def reserve_dispatch(budgets, *, tool=False, reserve=True):
79
+ """All enclosing allowances are checked under ordered locks before any debit."""
80
+ check_budgets(budgets)
81
+ ordered=sorted(budgets,key=id)
82
+ for budget in ordered: budget._lock.acquire()
83
+ try:
84
+ for budget in budgets:
85
+ budget._check_tokens(dispatch=True)
86
+ count=budget._tool_calls if tool else budget._provider_requests
87
+ cap=budget._tool_cap if tool else budget._provider_cap
88
+ if count>=cap:
89
+ raise InvocationBudgetExceeded('tool_call_limit' if tool else 'provider_request_limit')
90
+ if reserve:
91
+ for budget in budgets:
92
+ if tool: budget._tool_calls+=1
93
+ else: budget._provider_requests+=1
94
+ finally:
95
+ for budget in reversed(ordered): budget._lock.release()
96
+
97
+
98
+ def record_shared_usage(budgets, usage):
99
+ # Retain all enclosing paid evidence before reporting any violated cap.
100
+ ordered=sorted(budgets,key=id)
101
+ for budget in ordered: budget._lock.acquire()
102
+ try:
103
+ for budget in budgets:
104
+ if type(usage) is not int or usage<0: budget._usage_complete=False
105
+ else: budget._reported_tokens+=usage
106
+ for budget in budgets: budget._check_tokens()
107
+ finally:
108
+ for budget in reversed(ordered): budget._lock.release()
@@ -1,5 +1,5 @@
1
1
  """Opaque host correlation, independent of billing attribution."""
2
- from dataclasses import dataclass, fields
2
+ from dataclasses import dataclass, fields, field
3
3
  from collections.abc import Callable
4
4
  import unicodedata
5
5
 
@@ -37,3 +37,25 @@ class ToolExecutionContext:
37
37
  deadline: float
38
38
  cancelled: Callable
39
39
  operation_key: str | None = None
40
+
41
+ accounting_context: object = field(default=None, repr=False)
42
+
43
+ def child_context(self, *, invocation_id, run_id=None, step_id=None):
44
+ """Derive nested attribution from the actual resolved parent, never tool arguments."""
45
+ from .accounting import ResolvedContext
46
+ if type(self.accounting_context) is not ResolvedContext:
47
+ raise ValueError('accounted parent context required')
48
+ return self.accounting_context.child(invocation_id=invocation_id,run_id=run_id,step_id=step_id)
49
+
50
+ def remaining_timeout(self, maximum):
51
+ """Narrow a child's timeout to this tool's original absolute deadline."""
52
+ import math
53
+ import time
54
+ from .events import Cancelled
55
+ from .tools import ToolDeadlineExceeded
56
+ if type(maximum) not in (int,float) or not math.isfinite(maximum) or maximum<=0:
57
+ raise ValueError('invalid timeout')
58
+ if self.cancelled(): raise Cancelled()
59
+ remaining=self.deadline-time.monotonic()
60
+ if remaining<=0: raise ToolDeadlineExceeded('tool deadline exceeded')
61
+ return min(maximum,remaining)
@@ -29,6 +29,7 @@ from .tool_effects import admit_tool_effects, ToolEffectFailure
29
29
  from .telemetry import ObserverEmitter, observation_context, observe, execution_started
30
30
  from .budgets import InvocationBudget, InvocationBudgetExceeded
31
31
  from .turn_capture import TurnOutputExceeded
32
+ from .execution_budget import active_budgets, check_budgets, reserve_dispatch, record_shared_usage
32
33
 
33
34
 
34
35
  class _NoRedirect(urllib.request.HTTPRedirectHandler):
@@ -55,7 +56,8 @@ class _BoundedMixin:
55
56
  self.usage_complete = True
56
57
  self.turns = 0
57
58
  self.custom_tools = custom_tools
58
- self.deadline = deadline
59
+ self.execution_budgets = active_budgets()
60
+ self.deadline = min([deadline, *(b.deadline for b in self.execution_budgets)])
59
61
  self.capture = capture
60
62
  self.budget = budget
61
63
  self.scope, self.tool_journal = scope, tool_journal
@@ -66,11 +68,13 @@ class _BoundedMixin:
66
68
  self._latest_usage_complete = None
67
69
  self._previous_usage_details = {}
68
70
  self._provider_attempts = 0
71
+ self.accounting_context = None
69
72
  self.turn_mode = False
70
73
  self.turn_capture = None
71
74
  self.known_usage_details = {}
72
75
 
73
76
  def check_boundary(self):
77
+ check_budgets(self.execution_budgets)
74
78
  check_cancelled()
75
79
  from .conformance import _check_suite_deadline
76
80
  _check_suite_deadline()
@@ -81,14 +85,23 @@ class _BoundedMixin:
81
85
  builtin = DisabledBuiltinRegistry() if cwd is None else super().make_registry(cwd, features, metadata)
82
86
  return BoundedToolRegistry(builtin,
83
87
  self.custom_tools, metadata.get('allowed_tools', ()), self.check_boundary,
84
- self.budget.before_tool if self.budget is not None else None,
88
+ self.before_tool_dispatch,
85
89
  self.tool_execution_options, index_all_calls=self.turn_mode)
86
90
 
91
+ def before_tool_dispatch(self):
92
+ if self.budget is not None:
93
+ self.budget._check_reported_allowance()
94
+ cap=self.budget.policy.max_tool_calls
95
+ if cap is not None and self.budget.tool_calls>=cap:
96
+ raise InvocationBudgetExceeded('tool_call_limit')
97
+ reserve_dispatch(self.execution_budgets,tool=True)
98
+ if self.budget is not None: self.budget.before_tool()
99
+
87
100
  def tool_execution_options(self):
88
101
  index = self.tool_call_index
89
102
  self.tool_call_index += 1
90
103
  options = dict(scope=self.scope, deadline=self.deadline, cancelled=self.cancelled,
91
- call_index=index, tool_journal=self.tool_journal)
104
+ call_index=index, tool_journal=self.tool_journal, accounting_context=self.accounting_context)
92
105
  if self.turn_capture is not None:
93
106
  options['on_completion'] = lambda outcome, status='completed', **refs: self.turn_capture.record_tool_completion(
94
107
  index, outcome, status, **refs)
@@ -98,7 +111,8 @@ class _BoundedMixin:
98
111
  details = super().usage_details(parsed)
99
112
  # Compatibility parsers omit invalid quantities. A strict cap must see
100
113
  # those fields before omission could turn them into trusted evidence.
101
- if self.turn_mode or (self.budget is not None and self.budget.policy.max_reported_tokens is not None):
114
+ if (self.turn_mode or any(b._token_cap is not None for b in self.execution_budgets)
115
+ or (self.budget is not None and self.budget.policy.max_reported_tokens is not None)):
102
116
  usage = parsed.get('usage') if isinstance(parsed, dict) else None
103
117
  if usage is not None:
104
118
  self._latest_usage_complete = _reported_usage(details) is not None
@@ -125,7 +139,8 @@ class _BoundedMixin:
125
139
  return value
126
140
  except (ValueError, TypeError, UnicodeError, RecursionError):
127
141
  raise InvalidToolRequest() from None
128
- if self.budget is not None and self.budget.policy.max_reported_tokens is not None:
142
+ if (any(b._token_cap is not None for b in self.execution_budgets)
143
+ or (self.budget is not None and self.budget.policy.max_reported_tokens is not None)):
129
144
  try:
130
145
  return strict_json(raw)
131
146
  except InvalidEvidence:
@@ -144,8 +159,12 @@ class _BoundedMixin:
144
159
  else:
145
160
  self.reported_token_usage = (self.reported_token_usage or 0) + usage
146
161
  self.known_usage_details = _merge_token_usage_details(self.known_usage_details, details or {})
162
+ local_error=None
147
163
  if self.budget is not None:
148
- self.budget.record_usage(usage)
164
+ try: self.budget.record_usage(usage)
165
+ except InvocationBudgetExceeded as exc: local_error=exc
166
+ record_shared_usage(self.execution_budgets,usage)
167
+ if local_error is not None: raise local_error
149
168
 
150
169
  def tool_call_summary(self, tool_call):
151
170
  function = tool_call.get('function')
@@ -168,6 +187,8 @@ class _BoundedMixin:
168
187
  def before_http_dispatch(self, timeout):
169
188
  self.check_boundary()
170
189
  timeout = min(timeout, self.deadline - time.monotonic())
190
+ if self.budget is not None: self.budget._check_provider()
191
+ reserve_dispatch(self.execution_budgets)
171
192
  if self.budget is not None:
172
193
  self.budget.before_provider()
173
194
  self._provider_attempts += 1
@@ -178,6 +199,7 @@ class _BoundedMixin:
178
199
  self.check_boundary()
179
200
  if self.budget is not None:
180
201
  self.budget._check_provider()
202
+ reserve_dispatch(self.execution_budgets,reserve=False)
181
203
  self._turn_usage_unknown = False
182
204
  self._latest_usage_complete = None
183
205
  self._previous_usage_details = {}
@@ -274,7 +296,7 @@ class _BoundedMixin:
274
296
  native_failure = (isinstance(turn, Result) and self._config.provider == 'anthropic' and
275
297
  (attempted or _reported_usage(turn.metadata.get('token_usage_details')) is not None))
276
298
  strict_attempt_failure = (isinstance(turn, Result) and self.budget is not None and
277
- (self.turn_mode or self.budget.policy.max_reported_tokens is not None) and
299
+ (self.turn_mode or self.execution_budgets or self.budget.policy.max_reported_tokens is not None) and
278
300
  attempted)
279
301
  if not isinstance(turn, Result) or native_failure or strict_attempt_failure:
280
302
  details = turn.metadata.get('token_usage_details') if isinstance(turn, Result) else turn.token_usage_details
@@ -627,6 +649,7 @@ class ProsaicRuntime:
627
649
  backend = backend_type(endpoint, policy.max_input_bytes, custom,
628
650
  started + policy.timeout_s, capture, _budget,
629
651
  _operation_context, _tool_journal, cancelled)
652
+ backend.accounting_context = _context
630
653
  request = Invocation(str(Path(cwd).resolve()) if cwd is not None else None,
631
654
  prompt, invocation_env, remaining, {"prompt_metadata": metadata})
632
655
  if _turn_prepared is None:
@@ -643,9 +666,10 @@ class ProsaicRuntime:
643
666
  # The OpenAI text-only path returns Result directly and bypasses
644
667
  # _post_chat_turn. Account its terminal usage once, after Capture
645
668
  # has persisted the response context, preserving the returned text.
646
- if _budget.enabled and not backend.turns and (
669
+ if (_budget.enabled or backend.execution_budgets) and not backend.turns and (
647
670
  result.exit_code == 0 or 'token_usage_details' in result.metadata or
648
- (policy.max_reported_tokens is not None and _budget.provider_requests)):
671
+ (policy.max_reported_tokens is not None and _budget.provider_requests) or
672
+ (backend.execution_budgets and backend._provider_attempts)):
649
673
  try:
650
674
  backend.record_turn_usage(result.metadata.get('token_usage_details'))
651
675
  except InvocationBudgetExceeded as exc:
@@ -180,7 +180,7 @@ def bounded_result(value, maximum):
180
180
 
181
181
  def execute_custom_tool(tool, raw_arguments, *, check_boundary, scope=None,
182
182
  deadline=None, cancelled=lambda: False, call_index=0, tool_journal=None,
183
- on_completion=None):
183
+ on_completion=None, accounting_context=None):
184
184
  check_boundary()
185
185
  try:
186
186
  arguments = parse_arguments(raw_arguments, tool.parameters, tool.max_argument_bytes)
@@ -196,7 +196,7 @@ def execute_custom_tool(tool, raw_arguments, *, check_boundary, scope=None,
196
196
  except (TypeError, ValueError, RecursionError, OverflowError):
197
197
  return {'status': 'error', 'error': 'invalid_arguments'}
198
198
  context = ToolExecutionContext(scope, tool.name, tool.version, call_index,
199
- digest, deadline, cancelled)
199
+ digest, deadline, cancelled, accounting_context=accounting_context)
200
200
 
201
201
  def invoke(callback, ctx=context):
202
202
  owned = deepcopy(arguments)
@@ -44,7 +44,7 @@ def test_canonical_order_owned_snapshot_and_source_version():
44
44
  encode = lambda value: json.dumps(value, sort_keys=True, ensure_ascii=False,
45
45
  separators=(',', ':'), allow_nan=False)
46
46
  assert encode(a) == encode(b)
47
- assert a['version'] == 1 and a['runtimeVersion'] == '0.9.1'
47
+ assert a['version'] == 1 and a['runtimeVersion'] == '0.9.2'
48
48
  assert tuple(case['id'] for case in a['cases']) == CASES
49
49
  assert a['cases'][0]['origin'] == 'fixture'
50
50
  assert a['cases'][1]['state'] == 'not_run'
@@ -14,7 +14,7 @@ import pytest
14
14
 
15
15
 
16
16
  @pytest.mark.parametrize("project,name,version,package,dependency,licenses", [
17
- (".", "b3-prosaic-runtime", "0.9.1", "prosaic_runtime", "b3-prosaic<0.5,>=0.4",
17
+ (".", "b3-prosaic-runtime", "0.9.2", "prosaic_runtime", "b3-prosaic<0.5,>=0.4",
18
18
  ("LICENSE", "LICENSE-MIT", "NOTICE.md")),
19
19
  ("adapters/postgres", "b3-prosaic-runtime-postgres", "0.3.0", "prosaic_runtime_postgres",
20
20
  "b3-prosaic-runtime<0.10,>=0.9", ("LICENSE", "LICENSE-MIT", "NOTICE.md")),