b3-prosaic-runtime 0.9.4__tar.gz → 0.9.6__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.4 → b3_prosaic_runtime-0.9.6}/CHANGELOG.md +7 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/PKG-INFO +39 -1
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/README.md +38 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/docs/tool-effects.md +9 -1
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/pyproject.toml +1 -1
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/scripts/conversation_wheel_smoke.py +1 -1
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/scripts/production_wheel_smoke.py +1 -1
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/b3_prosaic_runtime.egg-info/PKG-INFO +39 -1
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/b3_prosaic_runtime.egg-info/SOURCES.txt +6 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/__init__.py +7 -2
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/accounting_capture.py +2 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/adapter.py +6 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/anthropic.py +2 -1
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/execution.py +28 -1
- b3_prosaic_runtime-0.9.6/src/prosaic_runtime/lifecycle.py +85 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/openai_compatible.py +17 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/runtime.py +12 -2
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/tool_effects.py +27 -1
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/tools.py +4 -4
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_conformance.py +1 -1
- b3_prosaic_runtime-0.9.6/tests/test_controlled_tool_failure.py +89 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_distribution_metadata.py +1 -1
- b3_prosaic_runtime-0.9.6/tests/test_finalization.py +191 -0
- b3_prosaic_runtime-0.9.6/tests/test_http_correlation.py +80 -0
- b3_prosaic_runtime-0.9.6/tests/test_lifecycle.py +70 -0
- b3_prosaic_runtime-0.9.6/tests/test_omlx_tool_envelope.py +60 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_package_compatibility.py +1 -1
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_tool_effects.py +1 -1
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/LICENSE +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/LICENSE-MIT +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/MANIFEST.in +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/NOTICE.md +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/docs/accounting.md +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/docs/adr-cli-sandbox.md +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/docs/cli-tools.md +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/docs/conformance.md +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/docs/conversations.md +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/docs/package-index.md +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/docs/production-acceptance.md +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/docs/release-0.7.1.md +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/docs/security.md +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/docs/structured-tools.md +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/docs/telemetry.md +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/docs/workflow-factory-runtime-implementation.md +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/examples/.prosaic/subagents/acquire-pilot.md +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/examples/.prosaic/subagents/acquired-reviewer.md +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/examples/.prosaic/subagents/catalog-reader.md +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/examples/.prosaic/subagents/cli-spec-reviewer.md +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/examples/.prosaic/subagents/launch-analyst.md +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/examples/.prosaic/subagents/launch-briefer.md +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/examples/.prosaic/subagents/launch-decision.md +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/examples/.prosaic/subagents/launch-skeptic.md +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/examples/.prosaic/subagents/native-summarizer.md +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/examples/.prosaic/subagents/preloaded-reviewer.md +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/examples/.prosaic/subagents/reviewer.md +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/examples/.prosaic/subagents/summarizer.md +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/examples/.prosaic/tools/analyze-spec.yml +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/examples/README.md +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/examples/anthropic.yml +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/examples/catalog.json +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/examples/catalog_tools.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/examples/cli-tool/prosaic_example_analyzer.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/examples/cli-tool/pyproject.toml +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/examples/cli-tools-sandboxed.yml +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/examples/cli-tools.yml +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/examples/custom-tools.yml +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/examples/evidence/launch/brief.md +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/examples/evidence/launch/field-notes.md +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/examples/evidence/launch/metrics.md +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/examples/evidence/pilot.md +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/examples/evidence/requirements.md +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/examples/prosaic-runtime.yaml +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/examples/run_acquisition.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/examples/run_cli_tool.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/examples/run_custom_tool.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/examples/run_examples.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/examples/run_sandbox_probe.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/examples/run_tiers.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/examples/tokenproxy.yml +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/examples/understanding/.prosaic/subagents/reviewer.md +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/examples/understanding/.prosaic/tools/understanding.yml +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/examples/understanding/runtime.yml +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/examples/with-tools.yml +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/scripts/release_artifacts.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/scripts/wheel_smoke.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/setup.cfg +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/b3_prosaic_runtime.egg-info/dependency_links.txt +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/b3_prosaic_runtime.egg-info/entry_points.txt +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/b3_prosaic_runtime.egg-info/requires.txt +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/b3_prosaic_runtime.egg-info/top_level.txt +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/accounting.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/admission.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/anthropic_stream.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/anthropic_usage.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/artifacts.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/budgets.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/cli.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/cli_tools.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/config.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/conformance.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/console.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/context_assembly.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/context_contracts.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/context_counters.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/context_execution.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/context_work.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/conversation.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/conversation_preparation.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/diagnostics.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/events.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/execution_budget.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/http_bounds.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/openai_compatible_compaction.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/openai_compatible_filters.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/openai_compatible_progress.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/openai_compatible_transcript.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/operation_context.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/policy.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/provider_types.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/sandbox.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/smoke_prose/pilot.md +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/smoke_prose/subagents/native-summarizer.md +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/smoke_prose/subagents/reviewer.md +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/smoke_prose/subagents/summarizer.md +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/structured_tools.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/telemetry.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/tool_registry.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/turn_capture.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/types.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/support/context_counter.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/support/conversation_server.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/support/tool_journal.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_accounting.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_acquisition.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_anthropic.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_anthropic_accounting.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_anthropic_package.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_anthropic_stream.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_artifact_admission.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_cli_sandbox.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_cli_tool_example.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_cli_tools.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_cli_tools_boundaries.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_config.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_context_assembly.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_context_contracts.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_context_execution.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_conversation_bounds.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_conversation_contracts.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_conversation_distribution.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_conversation_failure_evidence.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_conversation_review_regressions.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_conversation_tool_batches.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_conversation_turns.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_custom_tool_example.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_custom_tool_transport.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_custom_tools.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_execution_budget.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_features.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_http_boundaries.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_input_boundary.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_invocation_budgets.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_production_smoke.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_prompt_injection.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_provider_execution.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_read_provenance.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_redirect_safety.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_release_artifacts.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_run_adapter.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_runtime.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_sample_program.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_sandbox_probe_example.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_structured_tools.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_telemetry.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_tier_examples.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_tool_effect_workers.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_transport.py +0 -0
- {b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/tests/test_usage_integrity.py +0 -0
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.6 — 2026-10-17
|
|
4
|
+
|
|
5
|
+
- Add opt-in host-controlled final-answer phases after a bounded number of tool rounds.
|
|
6
|
+
- Add optional oMLX native tool-call envelope constraints, removed for final answers.
|
|
7
|
+
- Validate envelope configuration before dispatch and reject further tool calls after finalization.
|
|
8
|
+
- Retain native grant, budget and argument checks; defaults remain unchanged.
|
|
9
|
+
|
|
3
10
|
## 0.9.3 — 2026-10-14
|
|
4
11
|
|
|
5
12
|
- Normalize negative provider timeout exit statuses to 124 in conversation turn
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: b3-prosaic-runtime
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.6
|
|
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
|
|
@@ -28,6 +28,42 @@ and the `prosaic-runtime` CLI remain unchanged. The separately installed recorde
|
|
|
28
28
|
is `b3-prosaic-runtime-postgres` **0.3.0**, selecting Runtime 0.9.x.
|
|
29
29
|
Candidates are not public index releases until the release qualification gate passes.
|
|
30
30
|
|
|
31
|
+
### Opt-in final-answer phase
|
|
32
|
+
|
|
33
|
+
The local `0.9.3+finalize.4` candidate adds an operator-controlled endpoint
|
|
34
|
+
feature for bounded lookup workflows:
|
|
35
|
+
|
|
36
|
+
```yaml
|
|
37
|
+
features:
|
|
38
|
+
finalize_after_tool_rounds: 1
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
After that many tool rounds, the next OpenAI-compatible or native Anthropic
|
|
42
|
+
request contains no tool declarations and receives trusted final-answer
|
|
43
|
+
guidance. A provider that still requests a tool fails with
|
|
44
|
+
`tool_finalization_not_honored` before another handler runs. Existing provider,
|
|
45
|
+
tool, token and time budgets still apply; allow capacity for the final request.
|
|
46
|
+
Cancellation or exhausted budgets can stop before that request. Missing usage
|
|
47
|
+
remains unknown. This setting does not assert that the evidence is sufficient
|
|
48
|
+
or that the final answer is correct.
|
|
49
|
+
|
|
50
|
+
The value must be an integer from one through the invocation's effective
|
|
51
|
+
`max_tool_rounds`. Omit it to retain existing multi-round and no-progress
|
|
52
|
+
behavior. Agent prose and model output cannot set this host configuration.
|
|
53
|
+
This candidate is for pinned-wheel qualification, not a published release.
|
|
54
|
+
|
|
55
|
+
For an oMLX endpoint serving Qwen JSON tool envelopes, the host can also set
|
|
56
|
+
`omlx_tool_call_envelope: true`. This requires an explicit
|
|
57
|
+
`finalize_after_tool_rounds` and requests oMLX's `structured_outputs.regex`
|
|
58
|
+
constraint while tools are offered. It forces a tool envelope rather than an
|
|
59
|
+
ordinary answer during that phase; the final request removes this constraint.
|
|
60
|
+
`json_mode` applies to the final answer and is suppressed during the tool phase.
|
|
61
|
+
Native Anthropic rejects this vendor-specific setting before dispatch. Other
|
|
62
|
+
servers may reject or ignore it; this is not a portable OpenAI protocol option.
|
|
63
|
+
The constraint covers the envelope, not argument correctness or task quality.
|
|
64
|
+
Runtime still validates native tool names, arguments, permissions and budgets.
|
|
65
|
+
Assistant text is never converted to a tool call by this setting.
|
|
66
|
+
|
|
31
67
|
The optional [stateless conversation API](docs/conversations.md) uses complete,
|
|
32
68
|
bounded portable history through `prepare_turn()` and `turn()`. It requires no
|
|
33
69
|
workspace for ordinary callbacks or tool-free turns. Durable sessions and human
|
|
@@ -695,3 +731,5 @@ budget as a durable ledger or reset uncertain allowances after a restart.
|
|
|
695
731
|
PostgreSQL accounting remains the authoritative provider-call evidence. Saved
|
|
696
732
|
host outcomes require a fenced ToolJournal or equivalent durable application
|
|
697
733
|
repository; interrupted effects must not be repeated automatically.
|
|
734
|
+
|
|
735
|
+
See [execution correlation and graceful drain](docs/execution-lifecycle.md) for the host shutdown contract.
|
|
@@ -6,6 +6,42 @@ and the `prosaic-runtime` CLI remain unchanged. The separately installed recorde
|
|
|
6
6
|
is `b3-prosaic-runtime-postgres` **0.3.0**, selecting Runtime 0.9.x.
|
|
7
7
|
Candidates are not public index releases until the release qualification gate passes.
|
|
8
8
|
|
|
9
|
+
### Opt-in final-answer phase
|
|
10
|
+
|
|
11
|
+
The local `0.9.3+finalize.4` candidate adds an operator-controlled endpoint
|
|
12
|
+
feature for bounded lookup workflows:
|
|
13
|
+
|
|
14
|
+
```yaml
|
|
15
|
+
features:
|
|
16
|
+
finalize_after_tool_rounds: 1
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
After that many tool rounds, the next OpenAI-compatible or native Anthropic
|
|
20
|
+
request contains no tool declarations and receives trusted final-answer
|
|
21
|
+
guidance. A provider that still requests a tool fails with
|
|
22
|
+
`tool_finalization_not_honored` before another handler runs. Existing provider,
|
|
23
|
+
tool, token and time budgets still apply; allow capacity for the final request.
|
|
24
|
+
Cancellation or exhausted budgets can stop before that request. Missing usage
|
|
25
|
+
remains unknown. This setting does not assert that the evidence is sufficient
|
|
26
|
+
or that the final answer is correct.
|
|
27
|
+
|
|
28
|
+
The value must be an integer from one through the invocation's effective
|
|
29
|
+
`max_tool_rounds`. Omit it to retain existing multi-round and no-progress
|
|
30
|
+
behavior. Agent prose and model output cannot set this host configuration.
|
|
31
|
+
This candidate is for pinned-wheel qualification, not a published release.
|
|
32
|
+
|
|
33
|
+
For an oMLX endpoint serving Qwen JSON tool envelopes, the host can also set
|
|
34
|
+
`omlx_tool_call_envelope: true`. This requires an explicit
|
|
35
|
+
`finalize_after_tool_rounds` and requests oMLX's `structured_outputs.regex`
|
|
36
|
+
constraint while tools are offered. It forces a tool envelope rather than an
|
|
37
|
+
ordinary answer during that phase; the final request removes this constraint.
|
|
38
|
+
`json_mode` applies to the final answer and is suppressed during the tool phase.
|
|
39
|
+
Native Anthropic rejects this vendor-specific setting before dispatch. Other
|
|
40
|
+
servers may reject or ignore it; this is not a portable OpenAI protocol option.
|
|
41
|
+
The constraint covers the envelope, not argument correctness or task quality.
|
|
42
|
+
Runtime still validates native tool names, arguments, permissions and budgets.
|
|
43
|
+
Assistant text is never converted to a tool call by this setting.
|
|
44
|
+
|
|
9
45
|
The optional [stateless conversation API](docs/conversations.md) uses complete,
|
|
10
46
|
bounded portable history through `prepare_turn()` and `turn()`. It requires no
|
|
11
47
|
workspace for ordinary callbacks or tool-free turns. Durable sessions and human
|
|
@@ -673,3 +709,5 @@ budget as a durable ledger or reset uncertain allowances after a restart.
|
|
|
673
709
|
PostgreSQL accounting remains the authoritative provider-call evidence. Saved
|
|
674
710
|
host outcomes require a fenced ToolJournal or equivalent durable application
|
|
675
711
|
repository; interrupted effects must not be repeated automatically.
|
|
712
|
+
|
|
713
|
+
See [execution correlation and graceful drain](docs/execution-lifecycle.md) for the host shutdown contract.
|
|
@@ -72,7 +72,7 @@ matching lowercase 64-hex signature and one of these closed states:
|
|
|
72
72
|
| State | Permitted fields | Runtime action |
|
|
73
73
|
| --- | --- | --- |
|
|
74
74
|
| `new` | Bounded nonempty `claim_token`; no outcome | Dispatch the effect once. |
|
|
75
|
-
| `replay` | No token; finite bounded `{'status': 'ok', 'result': ...}` outcome | Copy and return the recorded outcome. |
|
|
75
|
+
| `replay` | No token; finite bounded `{'status': 'ok', 'result': ...}` or `{'status': 'error', 'error': code}` outcome | Copy and return the recorded outcome. |
|
|
76
76
|
| `uncertain` | No token or outcome | Stop pending explicit host resolution. |
|
|
77
77
|
|
|
78
78
|
The journal must atomically claim the namespace/key address and commit using a
|
|
@@ -112,3 +112,11 @@ or domain idempotency/fencing.
|
|
|
112
112
|
The SQLite journal under `tests/support/` is test support only. Production business
|
|
113
113
|
journals belong to host applications; usage recorders and Harness checkpoints are
|
|
114
114
|
separate stores and responsibilities.
|
|
115
|
+
|
|
116
|
+
A callback may return the public `ToolFailure("read_unavailable")` only for a
|
|
117
|
+
proven completed failure. Its bounded machine code is journaled before cancellation
|
|
118
|
+
or deadline checks; completion evidence is `completed` or `replayed`, never an
|
|
119
|
+
assertion that the requested business operation succeeded. NEVER return this type
|
|
120
|
+
for an unknown effect. ALWAYS leave generic exceptions and lost acknowledgements
|
|
121
|
+
on the uncertain-effect path. Ordinary returned dictionaries remain successful
|
|
122
|
+
JSON data and are not interpreted as this explicit failure contract.
|
|
@@ -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.6"
|
|
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.6'),
|
|
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.6"),
|
|
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.4 → b3_prosaic_runtime-0.9.6}/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.6
|
|
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
|
|
@@ -28,6 +28,42 @@ and the `prosaic-runtime` CLI remain unchanged. The separately installed recorde
|
|
|
28
28
|
is `b3-prosaic-runtime-postgres` **0.3.0**, selecting Runtime 0.9.x.
|
|
29
29
|
Candidates are not public index releases until the release qualification gate passes.
|
|
30
30
|
|
|
31
|
+
### Opt-in final-answer phase
|
|
32
|
+
|
|
33
|
+
The local `0.9.3+finalize.4` candidate adds an operator-controlled endpoint
|
|
34
|
+
feature for bounded lookup workflows:
|
|
35
|
+
|
|
36
|
+
```yaml
|
|
37
|
+
features:
|
|
38
|
+
finalize_after_tool_rounds: 1
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
After that many tool rounds, the next OpenAI-compatible or native Anthropic
|
|
42
|
+
request contains no tool declarations and receives trusted final-answer
|
|
43
|
+
guidance. A provider that still requests a tool fails with
|
|
44
|
+
`tool_finalization_not_honored` before another handler runs. Existing provider,
|
|
45
|
+
tool, token and time budgets still apply; allow capacity for the final request.
|
|
46
|
+
Cancellation or exhausted budgets can stop before that request. Missing usage
|
|
47
|
+
remains unknown. This setting does not assert that the evidence is sufficient
|
|
48
|
+
or that the final answer is correct.
|
|
49
|
+
|
|
50
|
+
The value must be an integer from one through the invocation's effective
|
|
51
|
+
`max_tool_rounds`. Omit it to retain existing multi-round and no-progress
|
|
52
|
+
behavior. Agent prose and model output cannot set this host configuration.
|
|
53
|
+
This candidate is for pinned-wheel qualification, not a published release.
|
|
54
|
+
|
|
55
|
+
For an oMLX endpoint serving Qwen JSON tool envelopes, the host can also set
|
|
56
|
+
`omlx_tool_call_envelope: true`. This requires an explicit
|
|
57
|
+
`finalize_after_tool_rounds` and requests oMLX's `structured_outputs.regex`
|
|
58
|
+
constraint while tools are offered. It forces a tool envelope rather than an
|
|
59
|
+
ordinary answer during that phase; the final request removes this constraint.
|
|
60
|
+
`json_mode` applies to the final answer and is suppressed during the tool phase.
|
|
61
|
+
Native Anthropic rejects this vendor-specific setting before dispatch. Other
|
|
62
|
+
servers may reject or ignore it; this is not a portable OpenAI protocol option.
|
|
63
|
+
The constraint covers the envelope, not argument correctness or task quality.
|
|
64
|
+
Runtime still validates native tool names, arguments, permissions and budgets.
|
|
65
|
+
Assistant text is never converted to a tool call by this setting.
|
|
66
|
+
|
|
31
67
|
The optional [stateless conversation API](docs/conversations.md) uses complete,
|
|
32
68
|
bounded portable history through `prepare_turn()` and `turn()`. It requires no
|
|
33
69
|
workspace for ordinary callbacks or tool-free turns. Durable sessions and human
|
|
@@ -695,3 +731,5 @@ budget as a durable ledger or reset uncertain allowances after a restart.
|
|
|
695
731
|
PostgreSQL accounting remains the authoritative provider-call evidence. Saved
|
|
696
732
|
host outcomes require a fenced ToolJournal or equivalent durable application
|
|
697
733
|
repository; interrupted effects must not be repeated automatically.
|
|
734
|
+
|
|
735
|
+
See [execution correlation and graceful drain](docs/execution-lifecycle.md) for the host shutdown contract.
|
{b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/b3_prosaic_runtime.egg-info/SOURCES.txt
RENAMED
|
@@ -94,6 +94,7 @@ src/prosaic_runtime/events.py
|
|
|
94
94
|
src/prosaic_runtime/execution.py
|
|
95
95
|
src/prosaic_runtime/execution_budget.py
|
|
96
96
|
src/prosaic_runtime/http_bounds.py
|
|
97
|
+
src/prosaic_runtime/lifecycle.py
|
|
97
98
|
src/prosaic_runtime/openai_compatible.py
|
|
98
99
|
src/prosaic_runtime/openai_compatible_compaction.py
|
|
99
100
|
src/prosaic_runtime/openai_compatible_filters.py
|
|
@@ -131,6 +132,7 @@ tests/test_conformance.py
|
|
|
131
132
|
tests/test_context_assembly.py
|
|
132
133
|
tests/test_context_contracts.py
|
|
133
134
|
tests/test_context_execution.py
|
|
135
|
+
tests/test_controlled_tool_failure.py
|
|
134
136
|
tests/test_conversation_bounds.py
|
|
135
137
|
tests/test_conversation_contracts.py
|
|
136
138
|
tests/test_conversation_distribution.py
|
|
@@ -144,9 +146,13 @@ tests/test_custom_tools.py
|
|
|
144
146
|
tests/test_distribution_metadata.py
|
|
145
147
|
tests/test_execution_budget.py
|
|
146
148
|
tests/test_features.py
|
|
149
|
+
tests/test_finalization.py
|
|
147
150
|
tests/test_http_boundaries.py
|
|
151
|
+
tests/test_http_correlation.py
|
|
148
152
|
tests/test_input_boundary.py
|
|
149
153
|
tests/test_invocation_budgets.py
|
|
154
|
+
tests/test_lifecycle.py
|
|
155
|
+
tests/test_omlx_tool_envelope.py
|
|
150
156
|
tests/test_package_compatibility.py
|
|
151
157
|
tests/test_production_smoke.py
|
|
152
158
|
tests/test_prompt_injection.py
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""Execute neutral Prosaic artifacts with explicitly granted authority."""
|
|
2
|
-
__version__ = '0.9.
|
|
2
|
+
__version__ = '0.9.6'
|
|
3
3
|
|
|
4
4
|
from .artifacts import ProsaicArtifact
|
|
5
5
|
from .config import EndpointConfig, RuntimeConfig, RunLimits, CliSandboxConfig
|
|
@@ -9,7 +9,7 @@ from .types import Result
|
|
|
9
9
|
from .tools import CustomTool, validate_custom_tools, custom_descriptors
|
|
10
10
|
from .admission import validate_execution_artifact
|
|
11
11
|
from .operation_context import InvocationScope, ToolExecutionContext
|
|
12
|
-
from .tool_effects import ToolClaim, ToolJournal, tool_journal_descriptor
|
|
12
|
+
from .tool_effects import ToolFailure, ToolClaim, ToolJournal, tool_journal_descriptor
|
|
13
13
|
from .telemetry import ObserverEmitter
|
|
14
14
|
from .conformance import evaluate_conformance
|
|
15
15
|
|
|
@@ -44,3 +44,8 @@ from .context_counters import (TokenCounter, TokenCounterRegistration,
|
|
|
44
44
|
__all__ += ["ContextPolicy", "TokenCounterDescriptor", "ContextAssemblyReport",
|
|
45
45
|
"ContextSelection", "ContextWorkState", "TokenCounter", "TokenCounterRegistration",
|
|
46
46
|
"TokenCounterRegistry"]
|
|
47
|
+
|
|
48
|
+
from .lifecycle import ExecutionLifecycle, Draining
|
|
49
|
+
__all__ += ["ExecutionLifecycle", "Draining"]
|
|
50
|
+
|
|
51
|
+
__all__ += ["ToolFailure"]
|
{b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/accounting_capture.py
RENAMED
|
@@ -44,6 +44,8 @@ class Capture:
|
|
|
44
44
|
self.recorder.prepare(intent)
|
|
45
45
|
except Exception as exc:
|
|
46
46
|
raise AccountingError('intent persistence failed') from exc
|
|
47
|
+
request.add_header("X-Correlation-ID", call_id)
|
|
48
|
+
request.add_header("X-Client-Request-Id", call_id)
|
|
47
49
|
self.call_ids.append(call_id)
|
|
48
50
|
observer = AnthropicObservation if self.provider == 'anthropic' else Observation
|
|
49
51
|
observation = observer(self.recorder, call_id, on_observation=self.on_observation)
|
|
@@ -11,6 +11,7 @@ import re
|
|
|
11
11
|
import time
|
|
12
12
|
|
|
13
13
|
from .runtime import ProsaicRuntime
|
|
14
|
+
from .lifecycle import tracked
|
|
14
15
|
from .policy import RunPolicy
|
|
15
16
|
from .types import Result
|
|
16
17
|
from .budgets import InvocationBudgetExceeded
|
|
@@ -162,6 +163,10 @@ class RuntimeAdapter:
|
|
|
162
163
|
def config(self):
|
|
163
164
|
return self.runtime.config
|
|
164
165
|
|
|
166
|
+
@property
|
|
167
|
+
def lifecycle(self):
|
|
168
|
+
return self.runtime.lifecycle
|
|
169
|
+
|
|
165
170
|
@property
|
|
166
171
|
def capabilities(self):
|
|
167
172
|
return self.runtime.capabilities | frozenset({'runtime_adapter_v1'})
|
|
@@ -174,6 +179,7 @@ class RuntimeAdapter:
|
|
|
174
179
|
def cli_tool_names(self):
|
|
175
180
|
return self.runtime.cli_tool_names
|
|
176
181
|
|
|
182
|
+
@tracked
|
|
177
183
|
def run(self, artifact, arguments='', *, policy=None, on_event=None, cancelled=None, **options):
|
|
178
184
|
self.validate_binding()
|
|
179
185
|
policy = policy or RunPolicy()
|
|
@@ -23,7 +23,8 @@ API_VERSION = '2023-06-01'
|
|
|
23
23
|
|
|
24
24
|
def validate_anthropic_controls(features, prompt_metadata):
|
|
25
25
|
"""Validate known Messages controls without constructing a model request."""
|
|
26
|
-
unsupported = {'json_mode', 'reasoning_effort', 'effort', 'thinking', 'stream_options', 'web_tools'
|
|
26
|
+
unsupported = {'json_mode', 'reasoning_effort', 'effort', 'thinking', 'stream_options', 'web_tools',
|
|
27
|
+
'omlx_tool_call_envelope'}
|
|
27
28
|
for name in unsupported:
|
|
28
29
|
value = features.get(name)
|
|
29
30
|
if value not in (None, False, 'off', 'false', 'disabled'):
|
|
@@ -57,6 +57,7 @@ class ExecutionBackend:
|
|
|
57
57
|
identical_tool_rounds = 0
|
|
58
58
|
tools_disabled = False
|
|
59
59
|
tool_no_progress_forced = False
|
|
60
|
+
finalization_forced = False
|
|
60
61
|
max_tool_rounds = _feature_int(
|
|
61
62
|
llm.features,
|
|
62
63
|
"max_tool_rounds",
|
|
@@ -71,6 +72,11 @@ class ExecutionBackend:
|
|
|
71
72
|
minimum=2,
|
|
72
73
|
maximum=10,
|
|
73
74
|
)
|
|
75
|
+
finalize_after = llm.features.get('finalize_after_tool_rounds')
|
|
76
|
+
if 'finalize_after_tool_rounds' in llm.features and (
|
|
77
|
+
type(finalize_after) is not int or not 1 <= finalize_after <= max_tool_rounds
|
|
78
|
+
):
|
|
79
|
+
raise ValueError('finalize_after_tool_rounds must be an integer between 1 and max_tool_rounds')
|
|
74
80
|
run_started = time.monotonic()
|
|
75
81
|
|
|
76
82
|
while True:
|
|
@@ -192,6 +198,21 @@ class ExecutionBackend:
|
|
|
192
198
|
},
|
|
193
199
|
)
|
|
194
200
|
if turn.tool_calls:
|
|
201
|
+
if finalization_forced:
|
|
202
|
+
return CliRunResult(
|
|
203
|
+
exit_code=1, stdout='',
|
|
204
|
+
stderr='model requested tools during final-answer phase',
|
|
205
|
+
token_usage=token_usage,
|
|
206
|
+
metadata={
|
|
207
|
+
'provider': self.name,
|
|
208
|
+
'provider_error_code': 'tool_finalization_not_honored',
|
|
209
|
+
'failure_reason': 'tool_finalization_not_honored',
|
|
210
|
+
'tool_call_count': tool_call_count,
|
|
211
|
+
'tool_rounds': tool_rounds,
|
|
212
|
+
'token_usage_details': token_usage_details,
|
|
213
|
+
**_transcript_metadata(transcript),
|
|
214
|
+
},
|
|
215
|
+
)
|
|
195
216
|
if initial_messages is not None:
|
|
196
217
|
if turn.finish_reason not in {'tool_calls', 'tool_use'}:
|
|
197
218
|
return CliRunResult(1, '', 'incomplete tool batch', token_usage=token_usage,
|
|
@@ -339,7 +360,13 @@ class ExecutionBackend:
|
|
|
339
360
|
if acquiring:
|
|
340
361
|
messages.append({'role': 'user', 'content': prompt_metadata['final_prompt']})
|
|
341
362
|
emit('acquisition_completed', tool=initial_tool, turn=turn_number)
|
|
342
|
-
if
|
|
363
|
+
if finalize_after is not None and tool_rounds >= finalize_after:
|
|
364
|
+
tools_disabled = True
|
|
365
|
+
finalization_forced = True
|
|
366
|
+
messages.append({'role': 'system', 'content':
|
|
367
|
+
'The tool phase has ended. Return the requested final answer using '
|
|
368
|
+
'the information available. Do not request any more tools.'})
|
|
369
|
+
elif identical_tool_rounds >= max_identical_tool_rounds:
|
|
343
370
|
tools_disabled = True
|
|
344
371
|
tool_no_progress_forced = True
|
|
345
372
|
initial_guidance = messages[0].get("content")
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Cooperative admission and drain for synchronous execution and persistence.
|
|
2
|
+
|
|
3
|
+
Hosts own signal handlers and must wait before closing stores or cancelling work.
|
|
4
|
+
A timeout reports unfinished work; it never cancels or kills admitted execution.
|
|
5
|
+
"""
|
|
6
|
+
from contextlib import contextmanager
|
|
7
|
+
from functools import wraps
|
|
8
|
+
import math
|
|
9
|
+
import threading
|
|
10
|
+
import time
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Draining(RuntimeError):
|
|
14
|
+
"""New root execution was refused because its lifecycle is draining."""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ExecutionLifecycle:
|
|
18
|
+
def __init__(self):
|
|
19
|
+
self._condition = threading.Condition()
|
|
20
|
+
self._local = threading.local()
|
|
21
|
+
self._draining = False
|
|
22
|
+
self._active = 0
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def draining(self):
|
|
26
|
+
with self._condition:
|
|
27
|
+
return self._draining
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def active(self):
|
|
31
|
+
with self._condition:
|
|
32
|
+
return self._active
|
|
33
|
+
|
|
34
|
+
@contextmanager
|
|
35
|
+
def work(self):
|
|
36
|
+
"""Admit synchronous work; nested work on the admitted thread may finish."""
|
|
37
|
+
depth = getattr(self._local, 'depth', 0)
|
|
38
|
+
with self._condition:
|
|
39
|
+
if self._draining and not depth:
|
|
40
|
+
raise Draining('service-draining')
|
|
41
|
+
self._active += 1
|
|
42
|
+
self._local.depth = depth + 1
|
|
43
|
+
try:
|
|
44
|
+
yield
|
|
45
|
+
finally:
|
|
46
|
+
self._local.depth = depth
|
|
47
|
+
with self._condition:
|
|
48
|
+
self._active -= 1
|
|
49
|
+
self._condition.notify_all()
|
|
50
|
+
|
|
51
|
+
def begin(self):
|
|
52
|
+
"""Permanently fence new root work. Does not cancel admitted work."""
|
|
53
|
+
with self._condition:
|
|
54
|
+
self._draining = True
|
|
55
|
+
self._condition.notify_all()
|
|
56
|
+
|
|
57
|
+
def wait(self, timeout=None):
|
|
58
|
+
"""Fence and wait through persistence; return False on an optional timeout.
|
|
59
|
+
|
|
60
|
+
Call from a separate supervisor thread, never inside admitted work.
|
|
61
|
+
Nested admission is thread-scoped, not inherited by new worker threads.
|
|
62
|
+
"""
|
|
63
|
+
if getattr(self._local, 'depth', 0):
|
|
64
|
+
raise RuntimeError('cannot wait for own admitted execution')
|
|
65
|
+
if timeout is not None and (isinstance(timeout, bool) or not isinstance(timeout, (int, float))
|
|
66
|
+
or not math.isfinite(timeout) or timeout < 0):
|
|
67
|
+
raise ValueError('timeout must be finite and nonnegative')
|
|
68
|
+
deadline = None if timeout is None else time.monotonic() + timeout
|
|
69
|
+
self.begin()
|
|
70
|
+
with self._condition:
|
|
71
|
+
while self._active:
|
|
72
|
+
remaining = None if deadline is None else deadline - time.monotonic()
|
|
73
|
+
if remaining is not None and remaining <= 0:
|
|
74
|
+
return False
|
|
75
|
+
self._condition.wait(remaining)
|
|
76
|
+
return True
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def tracked(method):
|
|
80
|
+
"""Track a synchronous operation on its owner's public lifecycle."""
|
|
81
|
+
@wraps(method)
|
|
82
|
+
def execute(self, *args, **kwargs):
|
|
83
|
+
with self.lifecycle.work():
|
|
84
|
+
return method(self, *args, **kwargs)
|
|
85
|
+
return execute
|
{b3_prosaic_runtime-0.9.4 → b3_prosaic_runtime-0.9.6}/src/prosaic_runtime/openai_compatible.py
RENAMED
|
@@ -229,6 +229,7 @@ class OpenAICompatibleBackend(ExecutionBackend):
|
|
|
229
229
|
|
|
230
230
|
def run_prompt(self, request: CliRunRequest) -> CliRunResult:
|
|
231
231
|
llm = self._config
|
|
232
|
+
_omlx_tool_envelope(llm.features)
|
|
232
233
|
assert llm.base_url is not None
|
|
233
234
|
assert llm.model is not None
|
|
234
235
|
prompt_metadata = _prompt_metadata(request)
|
|
@@ -409,6 +410,12 @@ class OpenAICompatibleBackend(ExecutionBackend):
|
|
|
409
410
|
if tools:
|
|
410
411
|
payload["tools"] = tools
|
|
411
412
|
payload["tool_choice"] = "auto"
|
|
413
|
+
envelope = _omlx_tool_envelope(llm.features)
|
|
414
|
+
if envelope and tools:
|
|
415
|
+
# oMLX constrains generation; only native tool_calls may execute.
|
|
416
|
+
# An answer JSON grammar cannot also constrain the tool envelope.
|
|
417
|
+
payload.pop('response_format', None)
|
|
418
|
+
payload['structured_outputs'] = {'regex': r'<tool_call>\s*\{[\s\S]*\}\s*</tool_call>'}
|
|
412
419
|
if streaming:
|
|
413
420
|
payload["stream"] = True
|
|
414
421
|
if _feature_enabled(llm.features, "stream_options", default=True):
|
|
@@ -1514,6 +1521,16 @@ def _api_key(
|
|
|
1514
1521
|
return token, ""
|
|
1515
1522
|
|
|
1516
1523
|
|
|
1524
|
+
def _omlx_tool_envelope(features):
|
|
1525
|
+
envelope = features.get('omlx_tool_call_envelope', False)
|
|
1526
|
+
if type(envelope) is not bool or (envelope and (
|
|
1527
|
+
type(features.get('finalize_after_tool_rounds')) is not int
|
|
1528
|
+
or features['finalize_after_tool_rounds'] < 1
|
|
1529
|
+
)):
|
|
1530
|
+
raise ValueError('omlx_tool_call_envelope requires a boolean and explicit finalize_after_tool_rounds')
|
|
1531
|
+
return envelope
|
|
1532
|
+
|
|
1533
|
+
|
|
1517
1534
|
def _feature_enabled(
|
|
1518
1535
|
features: dict[str, object], name: str, *, default: bool
|
|
1519
1536
|
) -> bool:
|
|
@@ -23,6 +23,7 @@ from .cli_tools import load_cli_tools, cli_tool_context
|
|
|
23
23
|
from .tool_registry import BoundedToolRegistry, DisabledBuiltinRegistry, InvalidToolRequest
|
|
24
24
|
from .accounting import AccountingError, resolve_context
|
|
25
25
|
from .accounting_capture import Capture, InvalidEvidence, strict_json
|
|
26
|
+
from .lifecycle import ExecutionLifecycle, tracked
|
|
26
27
|
from .http_bounds import LimitExceeded, set_response_timeout, BoundedNativeStream
|
|
27
28
|
from .operation_context import InvocationScope
|
|
28
29
|
from .tool_effects import admit_tool_effects, ToolEffectFailure
|
|
@@ -209,6 +210,10 @@ class _BoundedMixin:
|
|
|
209
210
|
observe('provider_request_started')
|
|
210
211
|
outcome = 'execution_failure'
|
|
211
212
|
status = None
|
|
213
|
+
if self.capture is None:
|
|
214
|
+
call_id = uuid4().hex
|
|
215
|
+
request.add_header("X-Correlation-ID", call_id)
|
|
216
|
+
request.add_header("X-Client-Request-Id", call_id)
|
|
212
217
|
opener = urllib.request.build_opener(_NoRedirect())
|
|
213
218
|
try:
|
|
214
219
|
# Observers and persisted intent preparation can stop execution.
|
|
@@ -338,9 +343,12 @@ class ProsaicRuntime:
|
|
|
338
343
|
capabilities = frozenset({'conformance_v1', 'invocation_budgets_v1', 'observer_v1', 'accounting_v1', 'read_receipts_v1', 'initial_tool_v1', 'initial_tool_enforcement_v1', 'acquisition_v1', 'custom_tools_v1', 'cli_tools_v1', 'cli_sandbox_v1'})
|
|
339
344
|
capabilities |= frozenset({'tool_context_v1', 'tool_journal_v1'})
|
|
340
345
|
capabilities |= frozenset({'conversation_turn_v1'})
|
|
341
|
-
capabilities |= frozenset({'context_assembly_v1'})
|
|
346
|
+
capabilities |= frozenset({'context_assembly_v1', 'http_correlation_v1', 'execution_lifecycle_v1'})
|
|
342
347
|
def __init__(self, config: RuntimeConfig, *, source=".prosaic", executable="prosaic", custom_tools=None,
|
|
343
|
-
accounting=None, context_defaults=None):
|
|
348
|
+
accounting=None, context_defaults=None, lifecycle=None):
|
|
349
|
+
self.lifecycle = lifecycle if lifecycle is not None else ExecutionLifecycle()
|
|
350
|
+
if not isinstance(self.lifecycle, ExecutionLifecycle):
|
|
351
|
+
raise TypeError("lifecycle must be an ExecutionLifecycle")
|
|
344
352
|
self.config = config
|
|
345
353
|
self.source = Path(source)
|
|
346
354
|
self.executable = executable
|
|
@@ -371,6 +379,7 @@ class ProsaicRuntime:
|
|
|
371
379
|
context_selection=context_selection, context_report=context_report,
|
|
372
380
|
context_work=context_work)
|
|
373
381
|
|
|
382
|
+
@tracked
|
|
374
383
|
def turn(self, artifact, *, message=None, history=(), input_data=None,
|
|
375
384
|
policy=None, limits=None, workspace=None, on_event=None, cancelled=None,
|
|
376
385
|
env=None, context=None, accounting=None, observer=None,
|
|
@@ -540,6 +549,7 @@ class ProsaicRuntime:
|
|
|
540
549
|
return self._preflight_names(names, str(Path(cwd).resolve()), policy,
|
|
541
550
|
dict(os.environ if env is None else env), deadline)
|
|
542
551
|
|
|
552
|
+
@tracked
|
|
543
553
|
def run(self, artifact: str | ProsaicArtifact, arguments: str = "", *,
|
|
544
554
|
cwd: str | Path = '.', policy: RunPolicy | None = None,
|
|
545
555
|
on_event=None, cancelled=None, env=None,
|
|
@@ -8,6 +8,28 @@ from typing import Protocol
|
|
|
8
8
|
from .operation_context import InvocationScope, bounded_reference
|
|
9
9
|
|
|
10
10
|
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class ToolFailure:
|
|
13
|
+
"""A trusted callback's proven completed failure, never an unknown effect.
|
|
14
|
+
|
|
15
|
+
Return only after establishing that the operation failed deterministically.
|
|
16
|
+
Generic exceptions and lost acknowledgements retain uncertain-effect fencing.
|
|
17
|
+
Codes are safe public machine identifiers, never raw exception text.
|
|
18
|
+
"""
|
|
19
|
+
code: str
|
|
20
|
+
|
|
21
|
+
def __post_init__(self):
|
|
22
|
+
if type(self.code) is not str or re.fullmatch(r'[a-z][a-z0-9_]{0,63}', self.code) is None:
|
|
23
|
+
raise ValueError('invalid controlled tool failure code')
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def failure_outcome(value):
|
|
27
|
+
if type(value) is ToolFailure:
|
|
28
|
+
ToolFailure.__post_init__(value)
|
|
29
|
+
return {'status': 'error', 'error': value.code}
|
|
30
|
+
return {'status': 'ok', 'result': value}
|
|
31
|
+
|
|
32
|
+
|
|
11
33
|
@dataclass(frozen=True)
|
|
12
34
|
class ToolClaim:
|
|
13
35
|
state: str
|
|
@@ -79,7 +101,11 @@ def canonical_json(value):
|
|
|
79
101
|
|
|
80
102
|
def snapshot_outcome(outcome, maximum):
|
|
81
103
|
from .tools import depth
|
|
82
|
-
if type(outcome) is not dict
|
|
104
|
+
if type(outcome) is not dict:
|
|
105
|
+
raise ValueError('invalid committed outcome')
|
|
106
|
+
if set(outcome)=={'status','error'} and outcome['status']=='error':
|
|
107
|
+
ToolFailure(outcome['error'])
|
|
108
|
+
elif set(outcome)!={'status','result'} or outcome['status']!='ok':
|
|
83
109
|
raise ValueError('invalid committed outcome')
|
|
84
110
|
depth(outcome)
|
|
85
111
|
serialized = canonical_json(outcome)
|
|
@@ -11,7 +11,7 @@ from jsonschema.exceptions import ValidationError, SchemaError
|
|
|
11
11
|
from .events import Cancelled
|
|
12
12
|
from .policy import BUILTIN_TOOLS
|
|
13
13
|
from .operation_context import ToolExecutionContext, bounded_reference
|
|
14
|
-
from .tool_effects import ToolEffectFailure, canonical_json, snapshot_outcome, validate_claim
|
|
14
|
+
from .tool_effects import ToolEffectFailure, canonical_json, snapshot_outcome, validate_claim, failure_outcome
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class ToolDeadlineExceeded(TimeoutError):
|
|
@@ -168,8 +168,8 @@ def parse_arguments(raw, schema, maximum):
|
|
|
168
168
|
|
|
169
169
|
def bounded_result(value, maximum):
|
|
170
170
|
try:
|
|
171
|
-
|
|
172
|
-
envelope
|
|
171
|
+
envelope = failure_outcome(value)
|
|
172
|
+
depth(envelope['result'] if envelope['status']=='ok' else envelope)
|
|
173
173
|
text = json.dumps(envelope, allow_nan=False)
|
|
174
174
|
if len(text.encode('utf-8')) > maximum:
|
|
175
175
|
return {'status': 'error', 'error': 'result_limit'}
|
|
@@ -250,7 +250,7 @@ def execute_custom_tool(tool, raw_arguments, *, check_boundary, scope=None,
|
|
|
250
250
|
check_boundary()
|
|
251
251
|
try:
|
|
252
252
|
value = invoke(tool.handler, context)
|
|
253
|
-
outcome = snapshot_outcome(
|
|
253
|
+
outcome = snapshot_outcome(failure_outcome(value), tool.max_result_bytes)
|
|
254
254
|
except Exception:
|
|
255
255
|
# Dispatch errors and unusable results cannot establish completion.
|
|
256
256
|
if on_completion is not None:
|
|
@@ -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.6'
|
|
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'
|