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.
Files changed (313) hide show
  1. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/CHANGELOG.md +18 -1
  2. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/PKG-INFO +1 -1
  3. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/docs/api.md +8 -2
  4. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/docs/coding-agent-backends.md +5 -0
  5. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/docs/design-drivers.md +20 -0
  6. nighthawk_python-0.17.0/docs/operation-feedback.md +229 -0
  7. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/docs/runtime-configuration.md +6 -4
  8. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/docs/specification.md +56 -10
  9. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/inspection_study.py +4 -4
  10. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/mkdocs.yml +1 -0
  11. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/pyproject.toml +1 -1
  12. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/backends/mcp_boundary.py +33 -6
  13. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/backends/mcp_server.py +4 -6
  14. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/backends/tool_bridge.py +16 -3
  15. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/lifecycle.py +2 -2
  16. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/oversight.py +30 -7
  17. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/__init__.py +14 -1
  18. nighthawk_python-0.17.0/src/nighthawk/runtime/model_response.py +43 -0
  19. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/operation_contract.py +33 -2
  20. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/operations.py +49 -19
  21. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/runner.py +19 -9
  22. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/step_context.py +2 -0
  23. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/step_executor.py +52 -23
  24. nighthawk_python-0.17.0/src/nighthawk/tools/_ordering.py +252 -0
  25. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/tools/contracts.py +6 -0
  26. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/tools/execution.py +31 -3
  27. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/backends/test_mcp_boundary.py +114 -90
  28. nighthawk_python-0.17.0/tests/backends/test_rejection_feedback.py +126 -0
  29. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/backends/test_tool_bridge.py +7 -7
  30. nighthawk_python-0.17.0/tests/docs/test_operation_feedback.py +15 -0
  31. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/lifecycle/test_step_completion.py +5 -5
  32. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/lifecycle/test_step_ledger.py +2 -2
  33. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/oversight/test_embedded_commit.py +4 -4
  34. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/oversight/test_return_expression.py +8 -8
  35. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/oversight/test_step_commit.py +6 -6
  36. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_managed_model.py +5 -4
  37. nighthawk_python-0.17.0/tests/runtime/test_operation_recovery.py +700 -0
  38. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_operations.py +8 -8
  39. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/tools/test_coroutine_ownership.py +2 -1
  40. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/tools/test_execution.py +8 -8
  41. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/tools/test_ordering.py +5 -5
  42. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/uv.lock +19 -19
  43. nighthawk_python-0.16.0/src/nighthawk/tools/_ordering.py +0 -88
  44. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/.claude/rules/docs.md +0 -0
  45. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/.claude/rules/evals.md +0 -0
  46. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/.claude/rules/promptfoo.md +0 -0
  47. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/.claude/rules/python.md +0 -0
  48. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/.claude/rules/src.md +0 -0
  49. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/.claude/rules/tests.md +0 -0
  50. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/.claude/settings.json +0 -0
  51. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/.claude/unset_envs.sh +0 -0
  52. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/.devcontainer/devcontainer-lock.json +0 -0
  53. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/.devcontainer/devcontainer.json +0 -0
  54. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/.devcontainer/postCreateCommand.sh +0 -0
  55. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/.github/dependabot.yml +0 -0
  56. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/.github/workflows/ci.yml +0 -0
  57. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/.github/workflows/docs.yml +0 -0
  58. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/.github/workflows/publish.yml +0 -0
  59. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/.gitignore +0 -0
  60. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/.python-version +0 -0
  61. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/AGENTS.md +0 -0
  62. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/CLAUDE.md +0 -0
  63. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/CONTRIBUTING.md +0 -0
  64. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/LICENSE +0 -0
  65. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/README.md +0 -0
  66. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/docs/AGENTS.md +0 -0
  67. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/docs/assets/nighthawk_logo-128x128.png +0 -0
  68. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/docs/embedded-execution.md +0 -0
  69. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/docs/executors.md +0 -0
  70. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/docs/for-coding-agents.md +0 -0
  71. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/docs/index.md +0 -0
  72. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/docs/natural-blocks.md +0 -0
  73. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/docs/patterns.md +0 -0
  74. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/docs/philosophy.md +0 -0
  75. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/docs/pydantic-ai-providers.md +0 -0
  76. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/docs/quickstart.md +0 -0
  77. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/docs/roadmap.md +0 -0
  78. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/docs/verification.md +0 -0
  79. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/AGENTS.md +0 -0
  80. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/coding_agents/evidence/20260914-guide-assessment.md +0 -0
  81. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/README.md +0 -0
  82. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/__init__.py +0 -0
  83. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/cases.py +0 -0
  84. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/evidence/20260908-tool-design-review.md +0 -0
  85. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/evidence/20260909-assessment.md +0 -0
  86. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/evidence/20260909-configurations.json +0 -0
  87. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/evidence/20260909-gpt56-assessment.md +0 -0
  88. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/evidence/20260909-gpt56-configurations.json +0 -0
  89. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/evidence/20260909-gpt56-guidance.md +0 -0
  90. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/evidence/20260909-gpt56-main.md +0 -0
  91. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/evidence/20260909-gpt56-selected-traces.json +0 -0
  92. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/evidence/20260909-gpt56-trials.csv +0 -0
  93. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/evidence/20260909-guidance.md +0 -0
  94. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/evidence/20260909-main.md +0 -0
  95. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/evidence/20260909-selected-traces.json +0 -0
  96. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/evidence/20260909-trials.csv +0 -0
  97. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/evidence/20260910-natural-composition.md +0 -0
  98. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/evidence/20260910-python-boundaries.md +0 -0
  99. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/evidence/20260911-ideal-state-audit.md +0 -0
  100. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/evidence/20260911-inspection-artifact-manifest.json +0 -0
  101. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/evidence/20260911-inspection-assessment.md +0 -0
  102. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/evidence/20260911-inspection-failure-traces.jsonl +0 -0
  103. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/evidence/20260911-inspection-study-protocol.md +0 -0
  104. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/evidence/20260911-inspection-trial-metrics.csv +0 -0
  105. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/prototype.py +0 -0
  106. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/report.py +0 -0
  107. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/model_tools/run.py +0 -0
  108. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/AGENTS.md +0 -0
  109. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/assertions/__init__.py +0 -0
  110. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/assertions/binding_value.py +0 -0
  111. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/assertions/outcome_kind.py +0 -0
  112. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/assertions/raise_message.py +0 -0
  113. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/evidence/2026-03-26-baseline-prompt-ab.md +0 -0
  114. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/evidence/2026-03-26-baseline-regression.md +0 -0
  115. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/evidence/2026-03-26-baseline-suffix-ab.md +0 -0
  116. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/evidence/2026-04-27-runtime-prompt-optimization.md +0 -0
  117. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/promptfooconfig-agents.yaml +0 -0
  118. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/promptfooconfig-luna-sanity.yaml +0 -0
  119. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/promptfooconfig-prompt-ab.yaml +0 -0
  120. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/promptfooconfig-suffix-ab.yaml +0 -0
  121. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/promptfooconfig.yaml +0 -0
  122. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/prompts/eval_coding_agent.txt +0 -0
  123. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/prompts/eval_default.txt +0 -0
  124. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/prompts/eval_legacy_default.txt +0 -0
  125. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/prompts/eval_mutation_aware.txt +0 -0
  126. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/prompts/eval_sequenced.txt +0 -0
  127. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/provider.py +0 -0
  128. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/research-result.md +0 -0
  129. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/test_cases/binding_operations.yaml +0 -0
  130. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/test_cases/edge_cases.yaml +0 -0
  131. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/test_cases/loop_outcomes.yaml +0 -0
  132. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/test_cases/loop_outcomes_ja.yaml +0 -0
  133. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/test_cases/multi_step.yaml +0 -0
  134. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/test_cases/null_handling.yaml +0 -0
  135. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/test_cases/outcome_kinds.yaml +0 -0
  136. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/evals/promptfoo/test_cases/tool_selection.yaml +0 -0
  137. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/pyrightconfig.json +0 -0
  138. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/AGENTS.md +0 -0
  139. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/__init__.py +0 -0
  140. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/backends/__init__.py +0 -0
  141. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/backends/_text_fallback.py +0 -0
  142. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/backends/base.py +0 -0
  143. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/backends/claude_code_cli.py +0 -0
  144. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/backends/claude_code_sdk.py +0 -0
  145. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/backends/claude_code_settings.py +0 -0
  146. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/backends/codex.py +0 -0
  147. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/backends/process_management.py +0 -0
  148. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/backends/text_projection.py +0 -0
  149. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/backends/tool_transport.py +0 -0
  150. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/composition.py +0 -0
  151. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/configuration.py +0 -0
  152. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/errors.py +0 -0
  153. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/function_call.py +0 -0
  154. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/identifier_path.py +0 -0
  155. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/json_renderer.py +0 -0
  156. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/natural/__init__.py +0 -0
  157. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/natural/_names.py +0 -0
  158. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/natural/blocks.py +0 -0
  159. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/natural/decorator.py +0 -0
  160. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/natural/definition.py +0 -0
  161. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/natural/transform.py +0 -0
  162. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/resilience/__init__.py +0 -0
  163. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/resilience/_budget.py +0 -0
  164. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/resilience/_circuit_breaker.py +0 -0
  165. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/resilience/_fallback.py +0 -0
  166. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/resilience/_retry.py +0 -0
  167. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/resilience/_timeout.py +0 -0
  168. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/resilience/_vote.py +0 -0
  169. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/_user_content.py +0 -0
  170. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/annotations.py +0 -0
  171. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/assignment.py +0 -0
  172. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/async_bridge.py +0 -0
  173. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/bindings.py +0 -0
  174. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/context.py +0 -0
  175. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/description.py +0 -0
  176. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/discovery.py +0 -0
  177. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/embedded.py +0 -0
  178. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/environment.py +0 -0
  179. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/execution_reference.py +0 -0
  180. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/expression.py +0 -0
  181. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/inspection.py +0 -0
  182. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/interruption.py +0 -0
  183. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/model_candidate.py +0 -0
  184. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/notifications.py +0 -0
  185. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/preparation.py +0 -0
  186. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/prompt.py +0 -0
  187. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/scoping.py +0 -0
  188. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/step_contract.py +0 -0
  189. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/step_definition.py +0 -0
  190. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/step_request.py +0 -0
  191. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/step_result.py +0 -0
  192. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/runtime/tool_calls.py +0 -0
  193. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/testing.py +0 -0
  194. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/tools/__init__.py +0 -0
  195. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/tools/_admission.py +0 -0
  196. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/tools/_tool_return_content.py +0 -0
  197. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/tools/assignment.py +0 -0
  198. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/tools/declarations.py +0 -0
  199. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/tools/provided.py +0 -0
  200. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/src/nighthawk/ulid.py +0 -0
  201. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/AGENTS.md +0 -0
  202. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/README.md +0 -0
  203. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/__init__.py +0 -0
  204. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/backends/__init__.py +0 -0
  205. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/backends/test_base.py +0 -0
  206. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/backends/test_claude_code_cli.py +0 -0
  207. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/backends/test_claude_code_sdk.py +0 -0
  208. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/backends/test_codex.py +0 -0
  209. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/backends/test_process_management.py +0 -0
  210. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/backends/test_text_fallback.py +0 -0
  211. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/backends/test_text_projection.py +0 -0
  212. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/backends/test_tool_transport.py +0 -0
  213. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/conftest.py +0 -0
  214. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/docs/__init__.py +0 -0
  215. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/docs/test_coding_agent_examples.py +0 -0
  216. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/docs/test_error_examples.py +0 -0
  217. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/docs/test_integrity.py +0 -0
  218. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/docs/test_links.py +0 -0
  219. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/docs/test_prompt_examples.py +0 -0
  220. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/docs/test_readme_example.py +0 -0
  221. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/docs/test_runtime_examples.py +0 -0
  222. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/evals/__init__.py +0 -0
  223. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/evals/model_tools/__init__.py +0 -0
  224. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/evals/model_tools/test_inspection_study.py +0 -0
  225. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/evals/model_tools/test_prototype.py +0 -0
  226. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/integration/__init__.py +0 -0
  227. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/integration/skip_helpers.py +0 -0
  228. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/integration/test_carry_pattern.py +0 -0
  229. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/integration/test_claude_code_cli.py +0 -0
  230. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/integration/test_claude_code_sdk.py +0 -0
  231. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/integration/test_codex.py +0 -0
  232. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/integration/test_natural_composition.py +0 -0
  233. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/integration/test_openai.py +0 -0
  234. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/integration/test_openai_multimodal.py +0 -0
  235. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/lifecycle/__init__.py +0 -0
  236. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/lifecycle/test_embedded.py +0 -0
  237. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/lifecycle/test_notifications.py +0 -0
  238. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/natural/__init__.py +0 -0
  239. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/natural/test_block_ordering.py +0 -0
  240. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/natural/test_blocks.py +0 -0
  241. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/natural/test_decorator_globals.py +0 -0
  242. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/natural/test_deferred_annotations.py +0 -0
  243. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/natural/test_function_definition.py +0 -0
  244. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/natural/test_introspection.py +0 -0
  245. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/natural/test_lexical_contract.py +0 -0
  246. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/natural/test_methods.py +0 -0
  247. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/natural/test_runtime_name_hygiene.py +0 -0
  248. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/natural/test_traceback.py +0 -0
  249. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/natural/test_variadic_contracts.py +0 -0
  250. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/oversight/__init__.py +0 -0
  251. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/resilience/__init__.py +0 -0
  252. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/resilience/test_budget.py +0 -0
  253. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/resilience/test_circuit_breaker.py +0 -0
  254. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/resilience/test_composition.py +0 -0
  255. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/resilience/test_fallback.py +0 -0
  256. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/resilience/test_retry.py +0 -0
  257. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/resilience/test_timeout.py +0 -0
  258. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/resilience/test_vote.py +0 -0
  259. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/__init__.py +0 -0
  260. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_assignment.py +0 -0
  261. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_async_bridge.py +0 -0
  262. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_bindings.py +0 -0
  263. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_capabilities.py +0 -0
  264. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_context.py +0 -0
  265. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_description.py +0 -0
  266. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_discovery.py +0 -0
  267. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_embedded.py +0 -0
  268. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_environment.py +0 -0
  269. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_executor_contract.py +0 -0
  270. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_explicit_sharing.py +0 -0
  271. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_expression.py +0 -0
  272. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_input_patterns.py +0 -0
  273. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_model_candidate.py +0 -0
  274. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_model_operation_failures.py +0 -0
  275. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_model_selection.py +0 -0
  276. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_output_selection.py +0 -0
  277. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_preparation.py +0 -0
  278. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_prompt_globals.py +0 -0
  279. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_prompt_multimodal.py +0 -0
  280. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_prompt_system.py +0 -0
  281. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_prompt_values.py +0 -0
  282. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_request_observation.py +0 -0
  283. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_runner.py +0 -0
  284. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_scope_composition.py +0 -0
  285. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_scoping.py +0 -0
  286. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_step_contract.py +0 -0
  287. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_step_definition.py +0 -0
  288. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_step_executor.py +0 -0
  289. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_tracing.py +0 -0
  290. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_usage_meter.py +0 -0
  291. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/runtime/test_workflows.py +0 -0
  292. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/support/__init__.py +0 -0
  293. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/support/codex.py +0 -0
  294. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/support/contexts.py +0 -0
  295. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/support/executors.py +0 -0
  296. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/support/host_controls.py +0 -0
  297. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/support/prompts.py +0 -0
  298. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/support/tools.py +0 -0
  299. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/support/values.py +0 -0
  300. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/test_composition.py +0 -0
  301. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/test_configuration.py +0 -0
  302. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/test_function_call.py +0 -0
  303. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/test_json_renderer.py +0 -0
  304. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/test_public_api.py +0 -0
  305. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/test_testing.py +0 -0
  306. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/tools/__init__.py +0 -0
  307. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/tools/test_admission.py +0 -0
  308. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/tools/test_assignment_async.py +0 -0
  309. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/tools/test_contracts.py +0 -0
  310. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/tools/test_declarations.py +0 -0
  311. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/tools/test_discovery_content.py +0 -0
  312. {nighthawk_python-0.16.0 → nighthawk_python-0.17.0}/tests/tools/test_provided_async.py +0 -0
  313. {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.16.0...HEAD
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.16.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
- - Reject
184
+ - RejectOperation
185
+ - AbortStep
185
186
  - Oversight
186
- - OversightRejectedError
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.Accept | nh.oversight.Reject:
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.Reject("Deletion requires host approval.")
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.Reject("A result is required.")
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
- Rejection terminates the step; it is not a model retry.
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
- `Reject` produces a failed terminal record before generated Python assignments.
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` or `Reject`, directly or asynchronously.
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: a rejected or terminally failed call stops subsequent validators as well as tool bodies.
941
- Operation dispatch preserves Pydantic AI validators, retries, deferrals, and completion strategies within the Nighthawk boundary.
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
- Operation rejection and inspection failures are terminal for that step.
947
- Later queued operations cannot execute their bodies, and an executor cannot suppress the failure and finalize a successful candidate.
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
- Model adapters resolve return expressions through this pipeline once before returning a candidate.
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`. Explicit rejection additionally carries `inspection_subject` and `rejection_reason`. Invalid hook decisions remain inspection failures.
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, oversight_rejection, raise_construction, or binding_assignment.
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, OversightRejectedError, Reject
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 | Reject:
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 Reject("Synthetic host policy rejected this action")
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 OversightRejectedError:
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,6 +1,6 @@
1
1
  [project]
2
2
  name = "nighthawk-python"
3
- version = "0.16.0"
3
+ version = "0.17.0"
4
4
  description = "A Python library where Python controls flow and LLMs or coding agents reason within constrained Natural blocks."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
@@ -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
- content_block_list = tool_handler_result_to_low_level_mcp_content(
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 content_block_list:
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
- return {"content": dumped_content_block_list}
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
- ) -> list[object]:
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 tool_handler_result_to_low_level_mcp_content(
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,