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.
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/CHANGELOG.md +8 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/PKG-INFO +43 -1
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/README.md +42 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/pyproject.toml +1 -1
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/scripts/conversation_wheel_smoke.py +1 -1
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/scripts/production_wheel_smoke.py +1 -1
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/b3_prosaic_runtime.egg-info/PKG-INFO +43 -1
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/b3_prosaic_runtime.egg-info/SOURCES.txt +2 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/__init__.py +4 -1
- b3_prosaic_runtime-0.9.2/src/prosaic_runtime/execution_budget.py +108 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/operation_context.py +23 -1
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/runtime.py +33 -9
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/tools.py +2 -2
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_conformance.py +1 -1
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_distribution_metadata.py +1 -1
- b3_prosaic_runtime-0.9.2/tests/test_execution_budget.py +128 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_package_compatibility.py +2 -2
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/LICENSE +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/LICENSE-MIT +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/MANIFEST.in +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/NOTICE.md +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/docs/accounting.md +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/docs/adr-cli-sandbox.md +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/docs/cli-tools.md +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/docs/conformance.md +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/docs/conversations.md +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/docs/package-index.md +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/docs/production-acceptance.md +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/docs/release-0.7.1.md +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/docs/security.md +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/docs/structured-tools.md +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/docs/telemetry.md +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/docs/tool-effects.md +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/docs/workflow-factory-runtime-implementation.md +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/.prosaic/subagents/acquire-pilot.md +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/.prosaic/subagents/acquired-reviewer.md +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/.prosaic/subagents/catalog-reader.md +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/.prosaic/subagents/cli-spec-reviewer.md +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/.prosaic/subagents/launch-analyst.md +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/.prosaic/subagents/launch-briefer.md +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/.prosaic/subagents/launch-decision.md +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/.prosaic/subagents/launch-skeptic.md +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/.prosaic/subagents/native-summarizer.md +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/.prosaic/subagents/preloaded-reviewer.md +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/.prosaic/subagents/reviewer.md +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/.prosaic/subagents/summarizer.md +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/.prosaic/tools/analyze-spec.yml +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/README.md +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/anthropic.yml +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/catalog.json +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/catalog_tools.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/cli-tool/prosaic_example_analyzer.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/cli-tool/pyproject.toml +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/cli-tools-sandboxed.yml +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/cli-tools.yml +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/custom-tools.yml +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/evidence/launch/brief.md +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/evidence/launch/field-notes.md +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/evidence/launch/metrics.md +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/evidence/pilot.md +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/evidence/requirements.md +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/prosaic-runtime.yaml +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/run_acquisition.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/run_cli_tool.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/run_custom_tool.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/run_examples.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/run_sandbox_probe.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/run_tiers.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/tokenproxy.yml +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/understanding/.prosaic/subagents/reviewer.md +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/understanding/.prosaic/tools/understanding.yml +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/understanding/runtime.yml +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/examples/with-tools.yml +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/scripts/release_artifacts.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/scripts/wheel_smoke.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/setup.cfg +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/b3_prosaic_runtime.egg-info/dependency_links.txt +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/b3_prosaic_runtime.egg-info/entry_points.txt +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/b3_prosaic_runtime.egg-info/requires.txt +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/b3_prosaic_runtime.egg-info/top_level.txt +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/accounting.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/accounting_capture.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/adapter.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/admission.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/anthropic.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/anthropic_stream.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/anthropic_usage.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/artifacts.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/budgets.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/cli.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/cli_tools.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/config.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/conformance.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/console.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/conversation.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/conversation_preparation.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/diagnostics.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/events.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/execution.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/http_bounds.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/openai_compatible.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/openai_compatible_compaction.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/openai_compatible_filters.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/openai_compatible_progress.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/openai_compatible_transcript.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/policy.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/provider_types.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/sandbox.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/smoke_prose/pilot.md +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/smoke_prose/subagents/native-summarizer.md +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/smoke_prose/subagents/reviewer.md +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/smoke_prose/subagents/summarizer.md +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/structured_tools.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/telemetry.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/tool_effects.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/tool_registry.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/turn_capture.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/types.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/support/conversation_server.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/support/tool_journal.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_accounting.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_acquisition.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_anthropic.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_anthropic_accounting.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_anthropic_package.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_anthropic_stream.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_artifact_admission.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_cli_sandbox.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_cli_tool_example.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_cli_tools.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_cli_tools_boundaries.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_config.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_conversation_bounds.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_conversation_contracts.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_conversation_distribution.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_conversation_failure_evidence.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_conversation_review_regressions.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_conversation_tool_batches.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_conversation_turns.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_custom_tool_example.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_custom_tool_transport.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_custom_tools.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_features.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_http_boundaries.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_input_boundary.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_invocation_budgets.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_production_smoke.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_prompt_injection.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_provider_execution.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_read_provenance.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_redirect_safety.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_release_artifacts.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_run_adapter.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_runtime.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_sample_program.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_sandbox_probe_example.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_structured_tools.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_telemetry.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_tier_examples.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_tool_effect_workers.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_tool_effects.py +0 -0
- {b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/tests/test_transport.py +0 -0
- {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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
{b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/b3_prosaic_runtime.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: b3-prosaic-runtime
|
|
3
|
-
Version: 0.9.
|
|
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.
|
{b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/b3_prosaic_runtime.egg-info/SOURCES.txt
RENAMED
|
@@ -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.
|
|
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()
|
{b3_prosaic_runtime-0.9.1 → b3_prosaic_runtime-0.9.2}/src/prosaic_runtime/operation_context.py
RENAMED
|
@@ -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.
|
|
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.
|
|
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 (
|
|
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
|
|
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.
|
|
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.
|
|
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")),
|