nighthawk-python 0.16.0__tar.gz → 0.17.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/CHANGELOG.md +18 -1
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/PKG-INFO +1 -1
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/docs/api.md +8 -2
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/docs/coding-agent-backends.md +5 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/docs/design-drivers.md +20 -0
- nighthawk_python-0.17.0/docs/operation-feedback.md +229 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/docs/runtime-configuration.md +6 -4
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/docs/specification.md +56 -10
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/inspection_study.py +4 -4
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/mkdocs.yml +1 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/pyproject.toml +1 -1
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/backends/mcp_boundary.py +33 -6
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/backends/mcp_server.py +4 -6
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/backends/tool_bridge.py +16 -3
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/lifecycle.py +2 -2
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/oversight.py +30 -7
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/__init__.py +14 -1
- nighthawk_python-0.17.0/src/nighthawk/runtime/model_response.py +43 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/operation_contract.py +33 -2
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/operations.py +49 -19
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/runner.py +19 -9
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/step_context.py +2 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/step_executor.py +52 -23
- nighthawk_python-0.17.0/src/nighthawk/tools/_ordering.py +252 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/tools/contracts.py +6 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/tools/execution.py +31 -3
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/backends/test_mcp_boundary.py +114 -90
- nighthawk_python-0.17.0/tests/backends/test_rejection_feedback.py +126 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/backends/test_tool_bridge.py +7 -7
- nighthawk_python-0.17.0/tests/docs/test_operation_feedback.py +15 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/lifecycle/test_step_completion.py +5 -5
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/lifecycle/test_step_ledger.py +2 -2
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/oversight/test_embedded_commit.py +4 -4
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/oversight/test_return_expression.py +8 -8
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/oversight/test_step_commit.py +6 -6
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_managed_model.py +5 -4
- nighthawk_python-0.17.0/tests/runtime/test_operation_recovery.py +700 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_operations.py +8 -8
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/tools/test_coroutine_ownership.py +2 -1
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/tools/test_execution.py +8 -8
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/tools/test_ordering.py +5 -5
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/uv.lock +19 -19
- nighthawk_python-0.16.0/src/nighthawk/tools/_ordering.py +0 -88
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/.claude/rules/docs.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/.claude/rules/evals.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/.claude/rules/promptfoo.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/.claude/rules/python.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/.claude/rules/src.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/.claude/rules/tests.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/.claude/settings.json +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/.claude/unset_envs.sh +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/.devcontainer/devcontainer-lock.json +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/.devcontainer/devcontainer.json +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/.devcontainer/postCreateCommand.sh +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/.github/dependabot.yml +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/.github/workflows/ci.yml +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/.github/workflows/docs.yml +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/.github/workflows/publish.yml +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/.gitignore +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/.python-version +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/AGENTS.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/CLAUDE.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/CONTRIBUTING.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/LICENSE +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/README.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/docs/AGENTS.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/docs/assets/nighthawk_logo-128x128.png +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/docs/embedded-execution.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/docs/executors.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/docs/for-coding-agents.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/docs/index.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/docs/natural-blocks.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/docs/patterns.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/docs/philosophy.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/docs/pydantic-ai-providers.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/docs/quickstart.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/docs/roadmap.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/docs/verification.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/AGENTS.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/coding_agents/evidence/20260914-guide-assessment.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/README.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/__init__.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/cases.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/evidence/20260908-tool-design-review.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/evidence/20260909-assessment.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/evidence/20260909-configurations.json +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/evidence/20260909-gpt56-assessment.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/evidence/20260909-gpt56-configurations.json +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/evidence/20260909-gpt56-guidance.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/evidence/20260909-gpt56-main.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/evidence/20260909-gpt56-selected-traces.json +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/evidence/20260909-gpt56-trials.csv +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/evidence/20260909-guidance.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/evidence/20260909-main.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/evidence/20260909-selected-traces.json +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/evidence/20260909-trials.csv +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/evidence/20260910-natural-composition.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/evidence/20260910-python-boundaries.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/evidence/20260911-ideal-state-audit.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/evidence/20260911-inspection-artifact-manifest.json +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/evidence/20260911-inspection-assessment.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/evidence/20260911-inspection-failure-traces.jsonl +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/evidence/20260911-inspection-study-protocol.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/evidence/20260911-inspection-trial-metrics.csv +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/prototype.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/report.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/run.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/AGENTS.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/assertions/__init__.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/assertions/binding_value.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/assertions/outcome_kind.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/assertions/raise_message.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/evidence/2026-03-26-baseline-prompt-ab.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/evidence/2026-03-26-baseline-regression.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/evidence/2026-03-26-baseline-suffix-ab.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/evidence/2026-04-27-runtime-prompt-optimization.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/promptfooconfig-agents.yaml +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/promptfooconfig-luna-sanity.yaml +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/promptfooconfig-prompt-ab.yaml +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/promptfooconfig-suffix-ab.yaml +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/promptfooconfig.yaml +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/prompts/eval_coding_agent.txt +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/prompts/eval_default.txt +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/prompts/eval_legacy_default.txt +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/prompts/eval_mutation_aware.txt +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/prompts/eval_sequenced.txt +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/provider.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/research-result.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/test_cases/binding_operations.yaml +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/test_cases/edge_cases.yaml +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/test_cases/loop_outcomes.yaml +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/test_cases/loop_outcomes_ja.yaml +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/test_cases/multi_step.yaml +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/test_cases/null_handling.yaml +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/test_cases/outcome_kinds.yaml +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/test_cases/tool_selection.yaml +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/pyrightconfig.json +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/AGENTS.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/__init__.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/backends/__init__.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/backends/_text_fallback.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/backends/base.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/backends/claude_code_cli.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/backends/claude_code_sdk.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/backends/claude_code_settings.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/backends/codex.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/backends/process_management.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/backends/text_projection.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/backends/tool_transport.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/composition.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/configuration.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/errors.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/function_call.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/identifier_path.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/json_renderer.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/natural/__init__.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/natural/_names.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/natural/blocks.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/natural/decorator.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/natural/definition.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/natural/transform.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/resilience/__init__.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/resilience/_budget.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/resilience/_circuit_breaker.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/resilience/_fallback.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/resilience/_retry.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/resilience/_timeout.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/resilience/_vote.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/_user_content.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/annotations.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/assignment.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/async_bridge.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/bindings.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/context.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/description.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/discovery.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/embedded.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/environment.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/execution_reference.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/expression.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/inspection.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/interruption.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/model_candidate.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/notifications.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/preparation.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/prompt.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/scoping.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/step_contract.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/step_definition.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/step_request.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/step_result.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/tool_calls.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/testing.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/tools/__init__.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/tools/_admission.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/tools/_tool_return_content.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/tools/assignment.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/tools/declarations.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/tools/provided.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/ulid.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/AGENTS.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/README.md +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/__init__.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/backends/__init__.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/backends/test_base.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/backends/test_claude_code_cli.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/backends/test_claude_code_sdk.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/backends/test_codex.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/backends/test_process_management.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/backends/test_text_fallback.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/backends/test_text_projection.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/backends/test_tool_transport.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/conftest.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/docs/__init__.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/docs/test_coding_agent_examples.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/docs/test_error_examples.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/docs/test_integrity.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/docs/test_links.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/docs/test_prompt_examples.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/docs/test_readme_example.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/docs/test_runtime_examples.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/evals/__init__.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/evals/model_tools/__init__.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/evals/model_tools/test_inspection_study.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/evals/model_tools/test_prototype.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/integration/__init__.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/integration/skip_helpers.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/integration/test_carry_pattern.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/integration/test_claude_code_cli.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/integration/test_claude_code_sdk.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/integration/test_codex.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/integration/test_natural_composition.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/integration/test_openai.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/integration/test_openai_multimodal.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/lifecycle/__init__.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/lifecycle/test_embedded.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/lifecycle/test_notifications.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/natural/__init__.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/natural/test_block_ordering.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/natural/test_blocks.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/natural/test_decorator_globals.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/natural/test_deferred_annotations.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/natural/test_function_definition.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/natural/test_introspection.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/natural/test_lexical_contract.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/natural/test_methods.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/natural/test_runtime_name_hygiene.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/natural/test_traceback.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/natural/test_variadic_contracts.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/oversight/__init__.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/resilience/__init__.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/resilience/test_budget.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/resilience/test_circuit_breaker.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/resilience/test_composition.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/resilience/test_fallback.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/resilience/test_retry.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/resilience/test_timeout.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/resilience/test_vote.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/__init__.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_assignment.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_async_bridge.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_bindings.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_capabilities.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_context.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_description.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_discovery.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_embedded.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_environment.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_executor_contract.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_explicit_sharing.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_expression.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_input_patterns.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_model_candidate.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_model_operation_failures.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_model_selection.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_output_selection.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_preparation.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_prompt_globals.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_prompt_multimodal.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_prompt_system.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_prompt_values.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_request_observation.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_runner.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_scope_composition.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_scoping.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_step_contract.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_step_definition.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_step_executor.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_tracing.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_usage_meter.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_workflows.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/support/__init__.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/support/codex.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/support/contexts.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/support/executors.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/support/host_controls.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/support/prompts.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/support/tools.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/support/values.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/test_composition.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/test_configuration.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/test_function_call.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/test_json_renderer.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/test_public_api.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/test_testing.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/tools/__init__.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/tools/test_admission.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/tools/test_assignment_async.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/tools/test_contracts.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/tools/test_declarations.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/tools/test_discovery_content.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/tools/test_provided_async.py +0 -0
- {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/tools/test_tool_return_content.py +0 -0
|
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.17.0]
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Breaking: operation inspection now returns `Accept`, `RejectOperation`, or `AbortStep`. Commit inspection accepts `Accept`, `Rewrite`, or `AbortStep`. Replace terminal `Reject(reason)` decisions with `AbortStep(reason)`; there is no legacy alias or implicit change to recoverable behavior.
|
|
15
|
+
- Breaking: `StepAbortedError` replaces `OversightRejectedError`. Terminal records use `failure_stage="oversight_abort"` and `abort_reason` instead of `oversight_rejection` and `rejection_reason`. Aborted operation records use status `"aborted"`; recoverable refusals use `"rejected"`.
|
|
16
|
+
- Breaking: explicit commit `AbortStep` decisions also propagate `StepAbortedError` instead of an `ExecutionError` wrapper, matching operation inspection; terminal delivery may still supply a host exception.
|
|
17
|
+
- Standard agent output processing resolves candidate expressions before leaving its model loop. Rejected return expressions can be corrected without recreating the step or replaying prior operations.
|
|
18
|
+
- Rejection stops the remaining calls in the same visible model response before argument validation, records them as `"skipped"`, and discards that response's final candidates. Nighthawk makes `early` dispatch follow emission order; ordinary Pydantic AI runs retain their own strategies.
|
|
19
|
+
- MCP/SDK tool responses carry error indicators and structured refusal information. Coding-agent internal batches and inference requests remain outside Core's visible response/request boundaries.
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- `OperationRejection`, `OperationRejectedError`, and exported `OperationDeliveryError` support direct executor recovery and correlated failure diagnostics. Refusals retain live state, earlier effects, request accounting, and ordinary finalization.
|
|
24
|
+
- Deterministic model, executor, lifecycle, ordering, and backend tests cover refusal feedback, skipped calls, cancellation, notification failures, limits, and nested execution. See [operation feedback](docs/operation-feedback.md) for examples and migration details.
|
|
25
|
+
|
|
10
26
|
## [0.16.0]
|
|
11
27
|
|
|
12
28
|
### Changed
|
|
@@ -364,7 +380,8 @@ Invalid initial results fail before oversight; use resilience retries to recover
|
|
|
364
380
|
- Step executor abstraction and provider integration foundation.
|
|
365
381
|
- Core documentation and project scaffolding.
|
|
366
382
|
|
|
367
|
-
[Unreleased]: https://github.com/kurusugawa-computer/nighthawk-python/compare/v0.
|
|
383
|
+
[Unreleased]: https://github.com/kurusugawa-computer/nighthawk-python/compare/v0.17.0...HEAD
|
|
384
|
+
[0.17.0]: https://github.com/kurusugawa-computer/nighthawk-python/compare/v0.15.0...v0.17.0
|
|
368
385
|
[0.16.0]: https://github.com/kurusugawa-computer/nighthawk-python/compare/v0.15.0...v0.16.0
|
|
369
386
|
[0.15.0]: https://github.com/kurusugawa-computer/nighthawk-python/compare/v0.14.0...v0.15.0
|
|
370
387
|
[0.14.0]: https://github.com/kurusugawa-computer/nighthawk-python/compare/v0.13.0...v0.14.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: nighthawk-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.17.0
|
|
4
4
|
Summary: A Python library where Python controls flow and LLMs or coding agents reason within constrained Natural blocks.
|
|
5
5
|
Project-URL: Repository, https://github.com/kurusugawa-computer/nighthawk-python
|
|
6
6
|
Project-URL: Documentation, https://kurusugawa-computer.github.io/nighthawk-python/
|
|
@@ -181,9 +181,11 @@
|
|
|
181
181
|
- StepCommit
|
|
182
182
|
- Rewrite
|
|
183
183
|
- Accept
|
|
184
|
-
-
|
|
184
|
+
- RejectOperation
|
|
185
|
+
- AbortStep
|
|
185
186
|
- Oversight
|
|
186
|
-
-
|
|
187
|
+
- StepAbortedError
|
|
188
|
+
- OperationDecision
|
|
187
189
|
- StepCommitDecision
|
|
188
190
|
|
|
189
191
|
## Lifecycle
|
|
@@ -257,6 +259,10 @@ See [Embedded execution](embedded-execution.md) for executable synchronous and a
|
|
|
257
259
|
- OperationRequest
|
|
258
260
|
- OperationFinished
|
|
259
261
|
- OperationError
|
|
262
|
+
- OperationRejection
|
|
263
|
+
- OperationRejectedError
|
|
264
|
+
- OperationDeliveryError
|
|
265
|
+
- OperationStatus
|
|
260
266
|
|
|
261
267
|
::: nighthawk.runtime.expression
|
|
262
268
|
options:
|
|
@@ -60,6 +60,11 @@ All three backends accept `working_directory` in their model settings. This abso
|
|
|
60
60
|
|
|
61
61
|
If a backend CLI process fails (e.g., crashes, times out, or returns an invalid response), Nighthawk surfaces the failure as an `ExecutionError`. See [Patterns](patterns.md#error-handling) for error handling patterns.
|
|
62
62
|
|
|
63
|
+
Operation inspection can return `RejectOperation` to deliver structured refusal through MCP/SDK error results while keeping the transport available for another inspected request.
|
|
64
|
+
`AbortStep`, inspection failures, and operation notification failures terminate the step even if the coding agent catches a tool error.
|
|
65
|
+
Core cannot identify a coding agent's invisible response batches or individually gate its internal inference requests.
|
|
66
|
+
The [operation feedback support matrix](operation-feedback.md#ordering-and-backend-boundaries) describes these limits and the distinct behavior of directly visible Pydantic AI responses.
|
|
67
|
+
|
|
63
68
|
### Allowed tool names
|
|
64
69
|
|
|
65
70
|
Use `allowed_tool_names` to restrict which Nighthawk tools are exposed to the backend via MCP. When `None` (default), all tools visible in the current scope are exposed.
|
|
@@ -187,6 +187,26 @@ The deterministic evidence covers absent parent bindings, preparation failures,
|
|
|
187
187
|
It does not establish that separating names prevents repeated model calls or improves judgments.
|
|
188
188
|
Assess those hypotheses through application evaluations, including independent reuse and the effort needed to declare shared dependencies.
|
|
189
189
|
|
|
190
|
+
## Correcting refused operations within a Natural step
|
|
191
|
+
|
|
192
|
+
**Status:** Execution contracts have deterministic coverage in `tests/runtime/test_operation_recovery.py` and `tests/backends/test_rejection_feedback.py`, with executable [model and custom-executor examples](operation-feedback.md).
|
|
193
|
+
No independent application case or comparative model-quality evaluation is recorded.
|
|
194
|
+
|
|
195
|
+
A host may refuse a proposed operation while still permitting a different way to complete the task.
|
|
196
|
+
Previously every operation-inspection rejection ended the step, even if an executor caught the exception.
|
|
197
|
+
Ordinary helper return values can communicate expected domain findings, but cannot change that Core-owned inspection and finalization contract.
|
|
198
|
+
Reimplementing the operation pipeline in application executors would duplicate state, notification, and termination responsibilities.
|
|
199
|
+
|
|
200
|
+
Core therefore separates `RejectOperation`, which refuses one body and exposes deliberate feedback, from `AbortStep`, which ends the inspected step.
|
|
201
|
+
The same step retains its live environment, prior effects, model history, and request controls while the model or custom executor chooses another operation.
|
|
202
|
+
Refusal records survive successful finalization; failed notifications and terminal stops cannot become ordinary feedback.
|
|
203
|
+
Visible model batches stop at refusal, while provider-internal batches remain a documented backend boundary.
|
|
204
|
+
|
|
205
|
+
Applications own the policy, alternative operations, acceptance criteria, and any durable evidence or improvement process.
|
|
206
|
+
Core does not infer that a later accepted output means the task was completed, automatically retry an operation, or add a separate repair agent or policy engine.
|
|
207
|
+
The deterministic tests establish execution behavior, not improved judgment, accumulated capability, or comparative advantage.
|
|
208
|
+
Further application evidence should compare successful corrections, unacceptable effects, authoring and maintenance burden, and inference cost against ordinary Python/model-library compositions.
|
|
209
|
+
|
|
190
210
|
## Using Nighthawk to develop Nighthawk
|
|
191
211
|
|
|
192
212
|
**Status:** Conceptual application direction.
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# Operation feedback
|
|
2
|
+
|
|
3
|
+
Use `RejectOperation` when an operation must not execute but another choice can still accomplish the task.
|
|
4
|
+
Use `AbortStep` when the current Natural step must end.
|
|
5
|
+
Both decisions run before the operation body, after admission and any native-tool argument validation.
|
|
6
|
+
Earlier effects, validators, and trusted inspection hooks are not rolled back.
|
|
7
|
+
|
|
8
|
+
Expected domain findings usually belong in ordinary Python return values.
|
|
9
|
+
For example, a search helper can return an empty result and a checker can return typed findings.
|
|
10
|
+
Operation oversight is the connection point for deciding whether an inspected operation may execute; Core does not supply domain policies, approval products, or automatic repair strategies.
|
|
11
|
+
|
|
12
|
+
## Decisions and records
|
|
13
|
+
|
|
14
|
+
| Decision | Operation body | Same step | Observation |
|
|
15
|
+
| --- | --- | --- | --- |
|
|
16
|
+
| `Accept()` | Runs normally | Continues | Ordinary operation result. |
|
|
17
|
+
| `RejectOperation(reason, guidance=None)` | Does not start | May choose another operation | `OperationFinished.status == "rejected"`, with `rejection`; direct APIs raise `OperationRejectedError`. |
|
|
18
|
+
| `AbortStep(reason)` | Does not start | Ends; success cannot replace the stop | `OperationFinished.status == "aborted"`; `StepAbortedError` and a failed terminal record. |
|
|
19
|
+
|
|
20
|
+
`OperationRejectedError.rejection` and the operation record share the same `OperationRejection`.
|
|
21
|
+
Its `request` identifies the operation and step; `reason` and `guidance` are deliberately model-visible.
|
|
22
|
+
`model_feedback()` returns those public fields and execution-start information without exposing live tool arguments or diagnostic exceptions.
|
|
23
|
+
Reasons are nonempty strings; guidance is an optional string.
|
|
24
|
+
|
|
25
|
+
An inspector must return a decision normally.
|
|
26
|
+
Unexpected exceptions, invalid decisions, and notification failures are terminal, including exceptions whose classes otherwise represent retries or recoverable input errors.
|
|
27
|
+
`OperationDeliveryError.finished` retains the operation fact and `delivery_cause` retains its notification failure.
|
|
28
|
+
Successful delivery is an in-process notification attempt, not an external storage transaction.
|
|
29
|
+
|
|
30
|
+
## Synchronous and asynchronous model examples
|
|
31
|
+
|
|
32
|
+
This deterministic example verifies execution semantics without a provider service.
|
|
33
|
+
It is not evidence that a particular model reliably chooses a good alternative.
|
|
34
|
+
|
|
35
|
+
```py
|
|
36
|
+
import asyncio
|
|
37
|
+
|
|
38
|
+
from pydantic_ai.messages import ModelMessage, ModelResponse, ToolCallPart, ToolReturnPart
|
|
39
|
+
from pydantic_ai.models.function import AgentInfo, FunctionModel
|
|
40
|
+
|
|
41
|
+
import nighthawk as nh
|
|
42
|
+
from nighthawk.oversight import Accept, OperationDecision, Oversight, RejectOperation
|
|
43
|
+
from nighthawk.runtime import OperationRequest
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
effects: list[str] = []
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def forbidden() -> int:
|
|
50
|
+
effects.append("forbidden body")
|
|
51
|
+
return 2
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def inspect_operation(request: OperationRequest) -> OperationDecision:
|
|
55
|
+
if request.expression == "forbidden()":
|
|
56
|
+
return RejectOperation("This helper is unavailable.", "Evaluate 1 + 1 instead.")
|
|
57
|
+
return Accept()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
async def inspect_operation_async(request: OperationRequest) -> OperationDecision:
|
|
61
|
+
await asyncio.sleep(0)
|
|
62
|
+
return inspect_operation(request)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@nh.natural_function
|
|
66
|
+
def calculate() -> int:
|
|
67
|
+
"""natural
|
|
68
|
+
Calculate the requested value using a permitted operation and return it.
|
|
69
|
+
"""
|
|
70
|
+
return 0
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@nh.natural_function
|
|
74
|
+
async def calculate_async() -> int:
|
|
75
|
+
"""natural
|
|
76
|
+
Calculate the requested value using a permitted operation and return it.
|
|
77
|
+
"""
|
|
78
|
+
return 0
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def make_executor() -> nh.AgentStepExecutor:
|
|
82
|
+
requests = 0
|
|
83
|
+
|
|
84
|
+
async def respond(messages: list[ModelMessage], information: AgentInfo) -> ModelResponse:
|
|
85
|
+
nonlocal requests
|
|
86
|
+
requests += 1
|
|
87
|
+
if requests == 1:
|
|
88
|
+
return ModelResponse(parts=[ToolCallPart("nh_eval", {"expression": "forbidden()"}, "proposal")])
|
|
89
|
+
assert requests == 2
|
|
90
|
+
assert any(isinstance(part, ToolReturnPart) and part.outcome == "denied" for message in messages for part in message.parts)
|
|
91
|
+
return ModelResponse(
|
|
92
|
+
parts=[ToolCallPart(information.output_tools[0].name, {"result": {"kind": "return", "return_expression": "1 + 1"}}, "finish")]
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
return nh.AgentStepExecutor.from_configuration(configuration=nh.StepExecutorConfiguration(model=FunctionModel(respond)))
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def run_sync_example() -> int:
|
|
99
|
+
with (
|
|
100
|
+
nh.run(make_executor()),
|
|
101
|
+
nh.scope(
|
|
102
|
+
task_references={"forbidden": forbidden},
|
|
103
|
+
oversight=Oversight(inspect_operation=inspect_operation),
|
|
104
|
+
),
|
|
105
|
+
):
|
|
106
|
+
return calculate()
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
async def run_async_example() -> int:
|
|
110
|
+
with (
|
|
111
|
+
nh.run(make_executor()),
|
|
112
|
+
nh.scope(
|
|
113
|
+
task_references={"forbidden": forbidden},
|
|
114
|
+
oversight=Oversight(inspect_operation=inspect_operation_async),
|
|
115
|
+
),
|
|
116
|
+
):
|
|
117
|
+
return await calculate_async()
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
assert run_sync_example() == 2
|
|
121
|
+
assert asyncio.run(run_async_example()) == 2
|
|
122
|
+
assert effects == []
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
The standard agent resolves return expressions inside its active output-processing loop as well.
|
|
126
|
+
If the model proposes `forbidden()` as its return expression, it can receive the same refusal and submit a different expression.
|
|
127
|
+
The resolved Python value then passes ordinary output validation and commit inspection without reevaluating the expression.
|
|
128
|
+
`inspect_step_commit` accepts only `Accept`, `Rewrite`, or `AbortStep`; a refused commit does not reopen inference.
|
|
129
|
+
Explicit `AbortStep` decisions propagate `StepAbortedError` from either inspector, unless terminal delivery raises a host exception.
|
|
130
|
+
Its `subject` identifies `"operation"` or `"step_commit"`.
|
|
131
|
+
|
|
132
|
+
## Custom executor example
|
|
133
|
+
|
|
134
|
+
This example extends the definitions above.
|
|
135
|
+
The executor owns its choice of the next operation and does not inspect or modify private failure state.
|
|
136
|
+
|
|
137
|
+
```py
|
|
138
|
+
from nighthawk.runtime import OperationRejectedError, Return, StepCandidate, StepRequest, eval_expression, eval_expression_async
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class CorrectingExecutor:
|
|
142
|
+
def run_step(self, *, request: StepRequest) -> StepCandidate:
|
|
143
|
+
try:
|
|
144
|
+
value = eval_expression(request.step_context, "forbidden()")
|
|
145
|
+
except OperationRejectedError as refusal:
|
|
146
|
+
assert refusal.rejection.reason == "This helper is unavailable."
|
|
147
|
+
value = eval_expression(request.step_context, "1 + 1")
|
|
148
|
+
return StepCandidate(outcome=Return(value))
|
|
149
|
+
|
|
150
|
+
async def run_step_async(self, *, request: StepRequest) -> StepCandidate:
|
|
151
|
+
try:
|
|
152
|
+
value = await eval_expression_async(request.step_context, "forbidden()")
|
|
153
|
+
except OperationRejectedError as refusal:
|
|
154
|
+
assert refusal.rejection.reason == "This helper is unavailable."
|
|
155
|
+
value = await eval_expression_async(request.step_context, "1 + 1")
|
|
156
|
+
return StepCandidate(outcome=Return(value))
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
with (
|
|
160
|
+
nh.run(CorrectingExecutor()),
|
|
161
|
+
nh.scope(
|
|
162
|
+
task_references={"forbidden": forbidden},
|
|
163
|
+
oversight=Oversight(inspect_operation=inspect_operation_async),
|
|
164
|
+
),
|
|
165
|
+
):
|
|
166
|
+
assert calculate() == 2
|
|
167
|
+
assert asyncio.run(calculate_async()) == 2
|
|
168
|
+
assert effects == []
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
A custom executor may instead report that no permitted solution exists, using its ordinary result contract.
|
|
172
|
+
Refusal does not force an automatic retry or prove that a task was completed.
|
|
173
|
+
Application acceptance checks still determine whether the final result is useful and its effects acceptable.
|
|
174
|
+
|
|
175
|
+
## Ordering and backend boundaries
|
|
176
|
+
|
|
177
|
+
For a visible model response containing operations A, B, C, and an output proposal, a refusal of B preserves completed A, records B as rejected, and records C and the output as skipped.
|
|
178
|
+
Skipped calls do not run argument validators, inspectors, or bodies.
|
|
179
|
+
Their `blocked_by_operation_id` points to B; skipped output proposals have request kind `"output"`.
|
|
180
|
+
The model receives refusal and skipped-call feedback before choosing a new response.
|
|
181
|
+
An earlier output candidate in that same response cannot override a later refusal; its completed expression effects are retained without replay.
|
|
182
|
+
Tool calls follow emission order inside Nighthawk, including when the borrowed agent selects `early`.
|
|
183
|
+
Pydantic AI's native-output precedence still applies: under `early`, structured text can be evaluated before co-emitted function calls; other strategies prioritize the calls.
|
|
184
|
+
A refusal at that native-output boundary skips the co-emitted calls and consumes the ordinary output retry budget.
|
|
185
|
+
|
|
186
|
+
| Execution route | Refusal feedback and correction | Batch boundary |
|
|
187
|
+
| --- | --- | --- |
|
|
188
|
+
| Standard Pydantic AI tools | Denied tool return, correlated feedback, same agent run. | Remaining calls in the visible response are skipped; `early` follows emission order inside Nighthawk. |
|
|
189
|
+
| Standard structured/native output | Rejected candidate expressions return through output retry handling, before leaving the agent run. | The same visible response barrier applies when output and function calls coexist. |
|
|
190
|
+
| Codex and Claude Code MCP/SDK tools | Error indicator and structured refusal, followed by separately inspected tool requests; transport remains open. | Internal provider response batches are not exposed, so Core cannot identify or skip invisible siblings. |
|
|
191
|
+
| Direct operation API / custom executor | Catch `OperationRejectedError` and choose the next operation explicitly. | The executor owns call grouping and ordering. |
|
|
192
|
+
| Borrowed protocol agent without Pydantic AI capabilities | The borrowed agent owns model feedback and candidate-recovery integration; an unhandled refusal propagates. | No automatic loop or replay is provided. |
|
|
193
|
+
|
|
194
|
+
Python function composition also defines nesting.
|
|
195
|
+
An `AbortStep` permanently ends its target step.
|
|
196
|
+
An uncaught child failure fails the parent operation; a Python helper may explicitly catch that child failure and return an application result.
|
|
197
|
+
It cannot change the child's terminal fact or resume that child execution.
|
|
198
|
+
Run-wide cancellation is a separate host responsibility.
|
|
199
|
+
|
|
200
|
+
## Limits and interruption
|
|
201
|
+
|
|
202
|
+
Correction retains working values, object identity, active policy, request history, and usage counters.
|
|
203
|
+
Each additional Core/Pydantic AI-visible request passes the existing capability admission and accounting boundary.
|
|
204
|
+
Function-tool refusals consume ordinary request/tool-call usage; rejected output expressions also consume the configured output retry budget.
|
|
205
|
+
Host stops and exhausted limits remain effective even if another output candidate was accepted earlier in that response.
|
|
206
|
+
|
|
207
|
+
The aggregate `UsageMeter` is not an in-progress request gate.
|
|
208
|
+
Use the existing [request usage and admission hooks](runtime-configuration.md#request-usage-and-admission) for controls between requests.
|
|
209
|
+
Coding-agent internal inference calls retain the backend's own limits and observability; this feature does not claim to observe them individually.
|
|
210
|
+
Unlimited configurations and arbitrary Python executor loops have no universal termination guarantee.
|
|
211
|
+
|
|
212
|
+
Interruption does not authorize detached processing or skipped cleanup.
|
|
213
|
+
Core retains its existing operation/terminal notification and asynchronous bridge responsibilities.
|
|
214
|
+
Application callbacks remain responsible for correctly shielding their own asynchronous cleanup under their execution framework's cancellation rules.
|
|
215
|
+
|
|
216
|
+
## Migration from 0.16
|
|
217
|
+
|
|
218
|
+
| Previous API | Replacement |
|
|
219
|
+
| --- | --- |
|
|
220
|
+
| Terminal `Reject(reason)` in either inspector | `AbortStep(reason)`. |
|
|
221
|
+
| `OversightRejectedError` | `StepAbortedError`. |
|
|
222
|
+
| Commit rejection caught as `ExecutionError` | Catch `StepAbortedError` for an explicit `AbortStep`; arbitrary commit-inspector failures retain the ordinary terminal error contract. |
|
|
223
|
+
| Operation rejection intended to permit another choice | Return `RejectOperation(reason, guidance=...)`; direct executors catch `OperationRejectedError`. |
|
|
224
|
+
| Terminal `failure_stage="oversight_rejection"` and `rejection_reason` | `failure_stage="oversight_abort"` and `abort_reason`. |
|
|
225
|
+
| Operation status `"rejected"` for a host termination | `"aborted"`; `"rejected"` now specifically identifies a recoverable operation refusal. |
|
|
226
|
+
| Assuming every operation record represents an attempted body | Check `status`, `phase`, and `evaluation_started`; later model calls may be `"skipped"`. |
|
|
227
|
+
|
|
228
|
+
The old symbols are removed; there is no compatibility mode that silently changes their meaning.
|
|
229
|
+
Operation and terminal records are host-owned evidence, not automatic snapshots of a mutable object graph or durable exactly-once storage.
|
|
@@ -330,15 +330,15 @@ import nighthawk as nh
|
|
|
330
330
|
from nighthawk.runtime import OperationRequest, Return
|
|
331
331
|
|
|
332
332
|
|
|
333
|
-
def inspect_operation(operation: OperationRequest) -> nh.oversight.
|
|
333
|
+
def inspect_operation(operation: OperationRequest) -> nh.oversight.OperationDecision:
|
|
334
334
|
if operation.kind == "native_tool" and operation.tool_name == "delete_file":
|
|
335
|
-
return nh.oversight.
|
|
335
|
+
return nh.oversight.RejectOperation("Deletion is unavailable.", "Choose an operation that preserves the file.")
|
|
336
336
|
return nh.oversight.Accept()
|
|
337
337
|
|
|
338
338
|
|
|
339
339
|
def inspect_step_commit(commit: nh.oversight.StepCommit) -> nh.oversight.StepCommitDecision:
|
|
340
340
|
if isinstance(commit.outcome, Return) and commit.outcome.value is None:
|
|
341
|
-
return nh.oversight.
|
|
341
|
+
return nh.oversight.AbortStep("A result is required.")
|
|
342
342
|
return nh.oversight.Accept()
|
|
343
343
|
|
|
344
344
|
|
|
@@ -350,7 +350,9 @@ oversight = nh.oversight.Oversight(
|
|
|
350
350
|
|
|
351
351
|
Install the configuration with `nh.scope(oversight=oversight)` inside an active run.
|
|
352
352
|
Operation inspection precedes execution and cannot undo earlier side effects.
|
|
353
|
-
|
|
353
|
+
`RejectOperation` returns deliberate feedback without executing that operation, allowing another choice within the same step.
|
|
354
|
+
`AbortStep` terminates the current step; catching its exception cannot make that step succeed.
|
|
355
|
+
The [operation feedback guide](operation-feedback.md) covers synchronous/asynchronous hooks, custom executors, model correction, and backend limits.
|
|
354
356
|
Commit inspection receives resolved `Pass`, `Return`, `Break`, `Continue`, or `Raise` outcomes and validated output values.
|
|
355
357
|
`Raise.error_type` is an exception class; its constructor runs only after inspection.
|
|
356
358
|
|
|
@@ -913,7 +913,9 @@ Its commit mapping is empty.
|
|
|
913
913
|
|
|
914
914
|
`Oversight.inspect_step_commit` receives one validated commit and may return its decision directly or asynchronously.
|
|
915
915
|
`Accept` preserves validated values without repeating validators.
|
|
916
|
-
`
|
|
916
|
+
`AbortStep(reason)` produces a failed terminal record before generated Python assignments and propagates `StepAbortedError`.
|
|
917
|
+
Commit inspection accepts `Accept`, `Rewrite`, or `AbortStep`; `RejectOperation` is invalid at this boundary.
|
|
918
|
+
Commit refusal never reopens model inference.
|
|
917
919
|
`Rewrite` accepts `outcome` and `binding_name_to_value`, each defaulting to `UNSET`; an empty rewrite is invalid.
|
|
918
920
|
A supplied mapping completely replaces the committed writes, including when it is empty.
|
|
919
921
|
`Rewrite(outcome=Return(None))` explicitly supplies `None`, subject to the return annotation and allowed-kind checks.
|
|
@@ -931,20 +933,44 @@ Finalization cannot undo earlier application calls, object mutation, or validato
|
|
|
931
933
|
|
|
932
934
|
### 10.3. Operation inspection
|
|
933
935
|
|
|
934
|
-
`Oversight.inspect_operation(OperationRequest)` returns `Accept
|
|
936
|
+
`Oversight.inspect_operation(OperationRequest)` returns `OperationDecision = Accept | RejectOperation | AbortStep`, directly or asynchronously.
|
|
935
937
|
Evaluation, assignment, model return-expression resolution, native tools, and inspection operations share this boundary.
|
|
936
938
|
The request includes the execution reference, operation Id, kind, and applicable expression, assignment target, tool arguments, and expected type.
|
|
937
939
|
Admission runs before inspection; inspection runs before any operation body or await of its result.
|
|
940
|
+
`Accept` permits execution.
|
|
941
|
+
`RejectOperation(reason, guidance=None)` prevents that operation's body and produces an `OperationRejection` with the request and deliberately model-visible feedback.
|
|
942
|
+
Direct operation APIs raise `OperationRejectedError`, whose `rejection` exposes that same record; a custom executor may catch it and choose another operation in the same step.
|
|
943
|
+
`OperationRejection.model_feedback()` exposes status, operation and step identities, reason, guidance, and `evaluation_started=False`, without serializing live arguments or diagnostic exceptions.
|
|
944
|
+
Reasons must be nonempty strings; guidance is an optional string.
|
|
945
|
+
`AbortStep(reason)` prevents execution and ends the current step with `StepAbortedError`.
|
|
946
|
+
It does not cancel an enclosing run or prohibit a calling Python helper from deliberately handling the child's failure.
|
|
947
|
+
An uncaught child failure also fails its parent operation; it is not a new inspection decision about the parent.
|
|
948
|
+
|
|
938
949
|
Requests execute serially within each step: in emission order for an ordered model batch, and in admission order otherwise.
|
|
939
950
|
Pydantic AI capability hooks cannot reorder an ordered batch across this boundary.
|
|
940
|
-
Argument validation is included in that ordering
|
|
941
|
-
|
|
951
|
+
Argument validation is included in that ordering.
|
|
952
|
+
After a recoverable rejection, the remaining calls in that model response are skipped before validation or execution, including output proposals and pending deferred calls.
|
|
953
|
+
Each skipped call receives an operation Id and an `OperationFinished(status="skipped", phase="admission", evaluation_started=False)` with `blocked_by_operation_id` referring to the refused operation.
|
|
954
|
+
Skipped output proposals use request kind `"output"`; an accepted candidate has no artificial output operation, but its expressions use the normal operation pipeline.
|
|
955
|
+
An earlier accepted candidate in the same response is discarded if a later operation is rejected; its already completed expression effects remain.
|
|
956
|
+
A subsequent model response makes new operation requests, each inspected normally.
|
|
957
|
+
Inside Nighthawk, Pydantic AI's `early` strategy follows emission order as `graceful` does, so an output cannot bypass an earlier proposed action.
|
|
958
|
+
`exhaustive` retains its output selection rules, subject to the rejection barrier and terminal limits.
|
|
959
|
+
Operation dispatch preserves Pydantic AI validators, retry budgets, and explicit deferral signals.
|
|
942
960
|
Pydantic AI runs outside that boundary use their own dispatch behavior.
|
|
943
961
|
Under `exhaustive`, upfront Pydantic AI function-call events have `args_valid=None` until the corresponding ordered validation runs; Nighthawk operation records retain the actual admission result.
|
|
944
962
|
Explicit Python concurrency inside a helper and separate child steps retain their own execution semantics.
|
|
963
|
+
Direct executor calls do not implicitly form a model-response batch; the executor owns their selection and ordering.
|
|
964
|
+
|
|
965
|
+
Recoverable rejection does not mark the step failed, restart it, roll back effects, repeat prior effects, or clear its working environment.
|
|
966
|
+
Operation identities differ across attempts; step identity and live application values remain intact.
|
|
967
|
+
Ordinary result validation, commit inspection, writeback, and one terminal delivery still apply.
|
|
968
|
+
A model's decision to finish after a rejection does not establish task correctness; application acceptance checks remain necessary.
|
|
945
969
|
|
|
946
|
-
|
|
947
|
-
Later queued operations cannot execute their bodies, and an executor cannot suppress
|
|
970
|
+
`AbortStep`, inspection failures, operation delivery failures, and interruptions remain terminal for that step.
|
|
971
|
+
Later queued operations cannot execute their bodies, and an executor cannot suppress a recorded terminal failure and finalize a successful candidate.
|
|
972
|
+
Only a normally returned `RejectOperation` decision authorizes recoverable oversight feedback.
|
|
973
|
+
An inspector raising `OperationRejectedError`, `OperationError`, or `ModelRetry` has failed; exception type alone never makes that inspection recoverable.
|
|
948
974
|
Ordinary exceptions from application helpers and native tools propagate to Python.
|
|
949
975
|
Runtime syntax, name-resolution, and value-validation errors provide recoverable operation feedback; Pydantic AI control signals retain their native meaning.
|
|
950
976
|
Classify failures by origin, not exception class: `NameError` or `ValueError` from a helper body remains an application exception.
|
|
@@ -952,7 +978,12 @@ An uncaught child Natural exception preserves its child terminal event and also
|
|
|
952
978
|
Return expected domain failures as ordinary helper results when the model should act on them; Python helpers may catch specific domain exceptions to construct those results.
|
|
953
979
|
Preserve explicit `ModelRetry`, `CallDeferred`, and `ApprovalRequired` signals through Pydantic AI machinery, and distinguish Pydantic validation failures from propagated validator exceptions.
|
|
954
980
|
Each completed operation delivers an `OperationFinished` fact with its phase, whether evaluation started, and its live result or original exception.
|
|
981
|
+
Statuses are `succeeded`, `rejected`, `aborted`, `skipped`, `failed`, and `interrupted`.
|
|
982
|
+
Recoverable rejection includes `rejection`; explicit `AbortStep` produces `aborted` and its original `StepAbortedError`.
|
|
955
983
|
A notification failure does not change that fact or replay the operation.
|
|
984
|
+
`OperationDeliveryError.finished` preserves the refusal or skipped-operation fact, and `delivery_cause` preserves the notification failure.
|
|
985
|
+
Notification failure is never ordinary model feedback, even if the callback raises a retry or rejection exception.
|
|
986
|
+
The existing interruption and cleanup responsibilities continue to apply.
|
|
956
987
|
|
|
957
988
|
Pydantic AI validates native and provided tool arguments before entering the tool body boundary.
|
|
958
989
|
A failed schema or argument-validator admission receives an operation Id and an `OperationFinished` record with `phase="admission"` and `evaluation_started=False`, retaining the original exception and unvalidated arguments.
|
|
@@ -961,13 +992,28 @@ Validation errors remain recoverable through Pydantic AI; ordinary exceptions pr
|
|
|
961
992
|
If a capability repairs invalid arguments, the failed admission remains recorded and the repaired request enters a separately identified inspected operation; the tool body runs once.
|
|
962
993
|
Approval and deferral during argument validation remain in Pydantic AI's designated machinery and do not fabricate a failed admission.
|
|
963
994
|
|
|
964
|
-
|
|
995
|
+
Standard Pydantic AI model adapters resolve candidate expressions through this pipeline inside output processing, before leaving the model loop.
|
|
996
|
+
Recoverable refusal becomes model-visible feedback, allowing a corrected expression in the same agent run and step.
|
|
997
|
+
Resolved candidates carry Python values to the runner; finalization never reevaluates their expressions.
|
|
998
|
+
Function-tool refusals use denied tool returns, distinct from successful values, and include structured operation correlation.
|
|
999
|
+
MCP and SDK transports set their error indicator and retain the canonical refusal feedback without lossy preview truncation.
|
|
1000
|
+
Coding-agent transports expose subsequent individual tool requests, but do not expose the provider's internal response batches; they cannot promise that invisible sibling calls are skipped.
|
|
1001
|
+
They do not silently convert recoverable refusal into a terminal error.
|
|
1002
|
+
Protocol agents supplied with `from_agent` that do not execute Pydantic AI capabilities own candidate-recovery integration; an unhandled rejection propagates instead of triggering automatic replay.
|
|
965
1003
|
Async Natural returns may await the expression result once; sync Natural returns reject awaitable expression results.
|
|
966
1004
|
Custom executors may directly construct `Return(value)` without evaluating an expression or creating an operation event.
|
|
967
1005
|
The runner independently validates the resolved value and all selected outputs before commit inspection.
|
|
968
1006
|
Expression-syntax policies belong in operation inspection; policies relating return values to committed outputs belong in commit inspection.
|
|
969
1007
|
A bindings-only rewrite can intentionally diverge from an earlier return value.
|
|
970
1008
|
|
|
1009
|
+
Correction requests retain the same request admission hooks, usage accounting, retry limits, and active configuration.
|
|
1010
|
+
Function-tool refusals consume ordinary requests and tool-call usage; output-expression refusals also consume the existing output retry budget.
|
|
1011
|
+
Limits and host stop decisions cannot be overridden by another candidate from the same response.
|
|
1012
|
+
No refusal-specific budget or universal termination guarantee is added for unlimited configurations or arbitrary custom executors.
|
|
1013
|
+
The request boundary is the Core/Pydantic AI-visible request; internal coding-agent inference calls retain their backend-specific controls.
|
|
1014
|
+
`UsageMeter` aggregates completed agent runs and is not an in-progress per-request gate; use the request capability boundary for that purpose.
|
|
1015
|
+
See [operation feedback](operation-feedback.md) for executable examples and the backend support matrix.
|
|
1016
|
+
|
|
971
1017
|
### 10.4. Terminal delivery and host ledgers
|
|
972
1018
|
|
|
973
1019
|
`nh.scope(lifecycle=nh.lifecycle.StepLifecycle(on_step_finished=callback))` installs a callback accepting `StepFinished` and returning `None` or an awaitable completion.
|
|
@@ -980,11 +1026,11 @@ Generated Python resolves the candidate, performs each selected write assignment
|
|
|
980
1026
|
|
|
981
1027
|
`StepFinished` is the discriminated union of frozen `StepCompleted`, `StepRaised`, `StepFailed`, and `StepInterrupted`, with respective `kind` values `completed`, `raised`, `failed`, and `interrupted`. All carry `execution_reference`, `processed_natural_program`, `input_binding_name_to_value`, `allowed_step_kinds`, and `assigned_binding_name_to_value`. Preparation-dependent fields are `None` when not established; empty mappings or tuples mean known empty values. Runtime references require both step identity fields. Mapping and tuple structure is snapshotted; contained Python objects retain their exact types and identity. Hosts must serialize or copy deliberately for durable records.
|
|
982
1028
|
|
|
983
|
-
`StepCompleted.outcome` is the final Pass/Return/Break/Continue. `StepRaised` holds a final Raise and its successfully constructed `exception`, including domain exceptions subclassing NighthawkError. Model-selected exception expressions use the operation pipeline; exception constructor failures are `StepFailed` at `raise_construction`, not domain raises. `StepFailed` holds `original_exception`, `failure_stage`, optional `attempted_outcome`, and optional `validated_binding_name_to_value`.
|
|
1029
|
+
`StepCompleted.outcome` is the final Pass/Return/Break/Continue. `StepRaised` holds a final Raise and its successfully constructed `exception`, including domain exceptions subclassing NighthawkError. Model-selected exception expressions use the operation pipeline; exception constructor failures are `StepFailed` at `raise_construction`, not domain raises. `StepFailed` holds `original_exception`, `failure_stage`, optional `attempted_outcome`, and optional `validated_binding_name_to_value`. An explicit `AbortStep` decision about this step additionally carries `inspection_subject` and `abort_reason`. Invalid hook decisions and exceptions raised by inspectors remain inspection failures.
|
|
984
1030
|
|
|
985
|
-
`FailureStage` identifies preparation, started_delivery, operation_inspection, operation_execution, operation_delivery, executor, outcome_validation, binding_validation, return_await, return_validation, commit_inspection, rewrite_validation,
|
|
1031
|
+
`FailureStage` identifies preparation, started_delivery, operation_inspection, operation_execution, operation_delivery, executor, outcome_validation, binding_validation, return_await, return_validation, commit_inspection, rewrite_validation, oversight_abort, raise_construction, or binding_assignment.
|
|
986
1032
|
Classification uses structured runtime state, not exception text.
|
|
987
|
-
Terminal failures recorded by the operation pipeline propagate unchanged; other internal execution failures use `ExecutionError(step_failed)` chained from the original exception unless delivery supplies a host exception.
|
|
1033
|
+
Terminal failures recorded by the operation pipeline and explicit commit `AbortStep` decisions propagate unchanged; other internal execution failures use `ExecutionError(step_failed)` chained from the original exception unless delivery supplies a host exception.
|
|
988
1034
|
DSL Raise propagates its prepared exception.
|
|
989
1035
|
|
|
990
1036
|
The callback may append the record to a host-owned authoritative ledger and raise a public host exception containing that exact stored event. For StepFailed, core explicitly chains the callback exception from `original_exception`; for StepRaised, from the prepared domain exception. If the callback rethrows the exact original exception object, core preserves its existing cause rather than chaining the exception to itself. Arbitrary callback exceptions are preserved and never reclassified or redelivered. `StepDeliveryError(execution_reference, cause)` is available for host adapters reporting storage failure; its `delivery_cause` does not imply a durable event exists. During ordinary execution failure, the outward chain prioritizes the execution exception and the storage cause remains available in `delivery_cause`.
|
|
@@ -33,7 +33,7 @@ from pydantic_ai.usage import UsageLimits
|
|
|
33
33
|
import nighthawk as nh
|
|
34
34
|
from nighthawk.json_renderer import render_json_text
|
|
35
35
|
from nighthawk.lifecycle import StepLifecycle
|
|
36
|
-
from nighthawk.oversight import Accept, Oversight,
|
|
36
|
+
from nighthawk.oversight import AbortStep, Accept, Oversight, StepAbortedError
|
|
37
37
|
from nighthawk.runtime import ContextRequest, OperationFinished, StepEnvironment, StepRequest, inspect_value
|
|
38
38
|
from nighthawk.runtime.environment import EnvironmentSource
|
|
39
39
|
from nighthawk.runtime.expression import compile_expression, evaluate_compiled_expression
|
|
@@ -134,7 +134,7 @@ class TrialState:
|
|
|
134
134
|
# Its Python helper reads that shared object, not an implicit parent environment.
|
|
135
135
|
self.workspace.step_name_to_value = self.context.environment.snapshot_working_state()
|
|
136
136
|
|
|
137
|
-
async def inspect_operation(self, request: Any) -> Accept |
|
|
137
|
+
async def inspect_operation(self, request: Any) -> Accept | AbortStep:
|
|
138
138
|
expression = request.expression or ""
|
|
139
139
|
called_names = set()
|
|
140
140
|
try:
|
|
@@ -155,7 +155,7 @@ class TrialState:
|
|
|
155
155
|
called_names.add(selected.args[0].value)
|
|
156
156
|
if "rejected_action" in called_names:
|
|
157
157
|
self.approvals.append("rejected")
|
|
158
|
-
return
|
|
158
|
+
return AbortStep("Synthetic host policy rejected this action")
|
|
159
159
|
if "approved_action" in called_names:
|
|
160
160
|
await asyncio.sleep(0)
|
|
161
161
|
self.approvals.append("approved")
|
|
@@ -429,7 +429,7 @@ async def run_trial(
|
|
|
429
429
|
value = TypeAdapter(state.workspace.output_name_to_type["result"]).validate_python(
|
|
430
430
|
state.context.environment.step_name_to_value["result"]
|
|
431
431
|
)
|
|
432
|
-
except
|
|
432
|
+
except StepAbortedError:
|
|
433
433
|
outcome = "rejected"
|
|
434
434
|
except Exception as exception:
|
|
435
435
|
exception_type = type(exception).__name__
|
|
@@ -67,6 +67,7 @@ nav:
|
|
|
67
67
|
- Natural blocks: natural-blocks.md
|
|
68
68
|
- Executors: executors.md
|
|
69
69
|
- Runtime configuration: runtime-configuration.md
|
|
70
|
+
- Operation feedback: operation-feedback.md
|
|
70
71
|
- Embedded execution: embedded-execution.md
|
|
71
72
|
- Patterns & verification:
|
|
72
73
|
- Patterns: patterns.md
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
-
from typing import cast
|
|
4
|
+
from typing import TYPE_CHECKING, cast
|
|
5
5
|
|
|
6
6
|
import tiktoken
|
|
7
7
|
from opentelemetry import context as otel_context
|
|
@@ -29,6 +29,9 @@ from ._text_fallback import (
|
|
|
29
29
|
)
|
|
30
30
|
from .tool_bridge import ToolHandler
|
|
31
31
|
|
|
32
|
+
if TYPE_CHECKING:
|
|
33
|
+
from mcp.types import CallToolResult
|
|
34
|
+
|
|
32
35
|
_logger = logging.getLogger("nighthawk")
|
|
33
36
|
|
|
34
37
|
|
|
@@ -187,27 +190,51 @@ def tool_handler_result_to_low_level_mcp_content(
|
|
|
187
190
|
return [mcp_types.TextContent(type="text", text=render_preview())]
|
|
188
191
|
|
|
189
192
|
|
|
193
|
+
def tool_handler_result_to_low_level_mcp_result(
|
|
194
|
+
*,
|
|
195
|
+
tool_name: str,
|
|
196
|
+
tool_handler_result: ToolHandlerResult,
|
|
197
|
+
rendering_policy: ToolResultRenderingPolicy | None = None,
|
|
198
|
+
) -> CallToolResult:
|
|
199
|
+
from mcp import types as mcp_types
|
|
200
|
+
|
|
201
|
+
content = tool_handler_result_to_low_level_mcp_content(
|
|
202
|
+
tool_name=tool_name, tool_handler_result=tool_handler_result, rendering_policy=rendering_policy
|
|
203
|
+
)
|
|
204
|
+
error = tool_handler_result["tool_outcome"]["error"] if tool_handler_result["kind"] == "tool_result" else None
|
|
205
|
+
return mcp_types.CallToolResult(
|
|
206
|
+
content=cast(list[mcp_types.ContentBlock], content),
|
|
207
|
+
is_error=tool_handler_result["kind"] != "tool_result" or error is not None,
|
|
208
|
+
structured_content={"error": dict(error)} if error is not None else None,
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
|
|
190
212
|
def _tool_handler_result_to_claude_code_sdk_content(
|
|
191
213
|
*,
|
|
192
214
|
tool_name: str,
|
|
193
215
|
tool_handler_result: ToolHandlerResult,
|
|
194
216
|
rendering_policy: ToolResultRenderingPolicy | None = None,
|
|
195
217
|
) -> dict[str, object]:
|
|
196
|
-
|
|
218
|
+
result = tool_handler_result_to_low_level_mcp_result(
|
|
197
219
|
tool_name=tool_name,
|
|
198
220
|
tool_handler_result=tool_handler_result,
|
|
199
221
|
rendering_policy=rendering_policy,
|
|
200
222
|
)
|
|
201
223
|
|
|
202
224
|
dumped_content_block_list: list[dict[str, object]] = []
|
|
203
|
-
for content_block in
|
|
225
|
+
for content_block in result.content:
|
|
204
226
|
model_dump = getattr(content_block, "model_dump", None)
|
|
205
227
|
if callable(model_dump):
|
|
206
228
|
dumped_content_block_list.append(cast(dict[str, object], model_dump(mode="json", by_alias=True, exclude_none=True)))
|
|
207
229
|
continue
|
|
208
230
|
raise TypeError(f"Unsupported MCP content block: {type(content_block).__name__}")
|
|
209
231
|
|
|
210
|
-
|
|
232
|
+
response: dict[str, object] = {"content": dumped_content_block_list}
|
|
233
|
+
if result.is_error:
|
|
234
|
+
response["isError"] = True
|
|
235
|
+
if result.structured_content is not None:
|
|
236
|
+
response["structuredContent"] = result.structured_content
|
|
237
|
+
return response
|
|
211
238
|
|
|
212
239
|
|
|
213
240
|
async def _get_tool_handler_result(
|
|
@@ -252,14 +279,14 @@ async def call_tool_for_low_level_mcp_server(
|
|
|
252
279
|
tool_handler: ToolHandler,
|
|
253
280
|
parent_otel_context: OtelContext,
|
|
254
281
|
rendering_policy: ToolResultRenderingPolicy | None = None,
|
|
255
|
-
) ->
|
|
282
|
+
) -> CallToolResult:
|
|
256
283
|
tool_handler_result = await _get_tool_handler_result(
|
|
257
284
|
tool_name=tool_name,
|
|
258
285
|
arguments=arguments,
|
|
259
286
|
tool_handler=tool_handler,
|
|
260
287
|
parent_otel_context=parent_otel_context,
|
|
261
288
|
)
|
|
262
|
-
return
|
|
289
|
+
return tool_handler_result_to_low_level_mcp_result(
|
|
263
290
|
tool_name=tool_name,
|
|
264
291
|
tool_handler_result=tool_handler_result,
|
|
265
292
|
rendering_policy=rendering_policy,
|