nighthawk-python 0.9.0__tar.gz → 0.11.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.9.0 → nighthawk_python-0.11.0}/CHANGELOG.md +45 -1
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/CONTRIBUTING.md +2 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/PKG-INFO +8 -8
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/docs/api.md +7 -3
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/docs/coding-agent-backends.md +12 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/docs/for-coding-agents.md +2 -1
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/docs/natural-blocks.md +15 -1
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/docs/patterns.md +2 -1
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/docs/philosophy.md +1 -1
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/docs/runtime-configuration.md +74 -4
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/docs/specification.md +74 -21
- nighthawk_python-0.11.0/evals/promptfoo/evidence/2026-04-27-runtime-prompt-optimization.md +77 -0
- nighthawk_python-0.11.0/evals/promptfoo/promptfooconfig-nano-sanity.yaml +39 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/evals/promptfoo/promptfooconfig-prompt-ab.yaml +9 -9
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/evals/promptfoo/promptfooconfig-suffix-ab.yaml +6 -6
- nighthawk_python-0.11.0/evals/promptfoo/prompts/eval_default.txt +25 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/evals/promptfoo/provider.py +5 -5
- nighthawk_python-0.11.0/evals/promptfoo/test_cases/loop_outcomes_ja.yaml +18 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/pyproject.toml +16 -16
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/src/nighthawk/__init__.py +20 -3
- nighthawk_python-0.11.0/src/nighthawk/backends/_text_fallback.py +141 -0
- nighthawk_python-0.11.0/src/nighthawk/backends/base.py +248 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/src/nighthawk/backends/claude_code_cli.py +16 -4
- nighthawk_python-0.11.0/src/nighthawk/backends/claude_code_sdk.py +307 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/src/nighthawk/backends/codex.py +21 -7
- nighthawk_python-0.11.0/src/nighthawk/backends/mcp_boundary.py +295 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/src/nighthawk/backends/mcp_server.py +19 -29
- nighthawk_python-0.11.0/src/nighthawk/backends/text_projection.py +352 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/src/nighthawk/backends/tool_bridge.py +102 -68
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/src/nighthawk/configuration.py +9 -3
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/src/nighthawk/json_renderer.py +17 -0
- nighthawk_python-0.11.0/src/nighthawk/oversight.py +180 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/src/nighthawk/resilience/__init__.py +2 -3
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/src/nighthawk/resilience/_vote.py +6 -5
- nighthawk_python-0.11.0/src/nighthawk/runtime/_user_content.py +101 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/src/nighthawk/runtime/prompt.py +332 -120
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/src/nighthawk/runtime/runner.py +210 -79
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/src/nighthawk/runtime/scoping.py +112 -34
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/src/nighthawk/runtime/step_context.py +15 -1
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/src/nighthawk/runtime/step_contract.py +13 -4
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/src/nighthawk/runtime/step_executor.py +24 -13
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/src/nighthawk/runtime/tool_calls.py +9 -6
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/src/nighthawk/testing.py +11 -16
- nighthawk_python-0.11.0/src/nighthawk/tools/_tool_return_content.py +66 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/src/nighthawk/tools/assignment.py +7 -4
- nighthawk_python-0.11.0/src/nighthawk/tools/contracts.py +195 -0
- nighthawk_python-0.11.0/src/nighthawk/tools/execution.py +239 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/backends/test_codex.py +125 -2
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/docs/test_docs_architecture.py +19 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/docs/test_prompt_examples.py +7 -4
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/execution/prompt_test_helpers.py +50 -1
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/execution/stub_executor.py +2 -2
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/execution/test_execution_outcome_prompt_fragment.py +16 -3
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/execution/test_globals_prompt.py +41 -7
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/execution/test_natural_block_ordering.py +3 -3
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/execution/test_natural_traceback.py +2 -1
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/execution/test_runtime.py +290 -9
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/execution/test_variables_prompt.py +520 -3
- nighthawk_python-0.11.0/tests/governance/test_governance.py +198 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/integration/test_llm_integration.py +159 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/public/test_public_api.py +74 -21
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/resilience/test_vote.py +8 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/tools/test_assignment_async.py +37 -0
- nighthawk_python-0.11.0/tests/tools/test_contracts.py +202 -0
- nighthawk_python-0.11.0/tests/tools/test_multimodal_helpers.py +167 -0
- nighthawk_python-0.11.0/tests/tools/test_tool_boundary.py +1897 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/uv.lock +264 -258
- nighthawk_python-0.9.0/src/nighthawk/backends/base.py +0 -129
- nighthawk_python-0.9.0/src/nighthawk/backends/claude_code_sdk.py +0 -289
- nighthawk_python-0.9.0/src/nighthawk/backends/mcp_boundary.py +0 -129
- nighthawk_python-0.9.0/src/nighthawk/tools/contracts.py +0 -72
- nighthawk_python-0.9.0/src/nighthawk/tools/execution.py +0 -83
- nighthawk_python-0.9.0/tests/tools/test_contracts.py +0 -106
- nighthawk_python-0.9.0/tests/tools/test_tool_boundary.py +0 -218
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/.claude/rules/docs.md +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/.claude/rules/promptfoo.md +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/.claude/rules/src.md +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/.claude/rules/tests.md +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/.claude/settings.json +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/.claude/unset_envs.sh +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/.devcontainer/Dockerfile.devcontainer +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/.devcontainer/Dockerfile.litellm +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/.devcontainer/devcontainer.json +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/.devcontainer/docker-compose.yaml +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/.devcontainer/litellm-config.yaml +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/.github/dependabot.yml +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/.github/workflows/ci.yml +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/.github/workflows/docs.yml +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/.github/workflows/publish.yml +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/.gitignore +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/.python-version +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/AGENTS.md +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/CLAUDE.md +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/LICENSE +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/README.md +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/docs/AGENTS.md +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/docs/assets/nighthawk_logo-128x128.png +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/docs/executors.md +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/docs/index.md +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/docs/pydantic-ai-providers.md +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/docs/quickstart.md +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/docs/roadmap.md +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/docs/verification.md +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/evals/promptfoo/assertions/__init__.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/evals/promptfoo/assertions/binding_value.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/evals/promptfoo/assertions/outcome_kind.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/evals/promptfoo/assertions/raise_message.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/evals/promptfoo/evidence/2026-03-26-baseline-prompt-ab.md +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/evals/promptfoo/evidence/2026-03-26-baseline-regression.md +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/evals/promptfoo/evidence/2026-03-26-baseline-suffix-ab.md +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/evals/promptfoo/promptfooconfig-agents.yaml +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/evals/promptfoo/promptfooconfig.yaml +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/evals/promptfoo/prompts/eval_coding_agent.txt +0 -0
- /nighthawk_python-0.9.0/evals/promptfoo/prompts/eval_default.txt → /nighthawk_python-0.11.0/evals/promptfoo/prompts/eval_legacy_default.txt +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/evals/promptfoo/prompts/eval_mutation_aware.txt +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/evals/promptfoo/prompts/eval_sequenced.txt +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/evals/promptfoo/research-result.md +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/evals/promptfoo/test_cases/binding_operations.yaml +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/evals/promptfoo/test_cases/edge_cases.yaml +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/evals/promptfoo/test_cases/loop_outcomes.yaml +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/evals/promptfoo/test_cases/multi_step.yaml +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/evals/promptfoo/test_cases/null_handling.yaml +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/evals/promptfoo/test_cases/outcome_kinds.yaml +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/evals/promptfoo/test_cases/tool_selection.yaml +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/mkdocs.yml +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/pyrightconfig.json +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/src/AGENTS.md +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/src/nighthawk/backends/__init__.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/src/nighthawk/backends/claude_code_settings.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/src/nighthawk/errors.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/src/nighthawk/identifier_path.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/src/nighthawk/natural/__init__.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/src/nighthawk/natural/blocks.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/src/nighthawk/natural/decorator.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/src/nighthawk/natural/transform.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/src/nighthawk/resilience/_budget.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/src/nighthawk/resilience/_circuit_breaker.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/src/nighthawk/resilience/_fallback.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/src/nighthawk/resilience/_retry.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/src/nighthawk/resilience/_timeout.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/src/nighthawk/runtime/__init__.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/src/nighthawk/runtime/async_bridge.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/src/nighthawk/tools/__init__.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/src/nighthawk/tools/provided.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/src/nighthawk/tools/registry.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/src/nighthawk/ulid.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/AGENTS.md +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/__init__.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/backends/__init__.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/backends/test_claude_code_cli.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/backends/test_claude_code_sdk.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/conftest.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/docs/__init__.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/docs/test_coding_agent_examples.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/execution/__init__.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/execution/test_infer_binding_types.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/integration/__init__.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/integration/skip_helpers.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/integration/test_carry_pattern.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/integration/test_claude_code_cli_integration.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/integration/test_claude_code_sdk_integration.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/integration/test_codex_integration.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/natural/__init__.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/natural/test_blocks.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/public/__init__.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/public/test_readme_example.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/public/test_usage_meter.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/resilience/__init__.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/resilience/test_budget.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/resilience/test_circuit_breaker.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/resilience/test_composition.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/resilience/test_fallback.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/resilience/test_retry.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/resilience/test_timeout.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/test_renderer.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/test_testing.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/tools/__init__.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/tools/test_provided_async.py +0 -0
- {nighthawk_python-0.9.0 → nighthawk_python-0.11.0}/tests/tools/test_registry.py +0 -0
|
@@ -7,7 +7,48 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.11.0]
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Native Pydantic AI multimodal binding support for Natural block prompts, including `BinaryContent`, URL-based media, `UploadedFile`, and explicit dotted multimodal references.
|
|
14
|
+
- Multimodal tool-result transport for coding-agent backends, preserving mixed text/media order and carrying MCP image/audio content natively where supported.
|
|
15
|
+
- Public scope snapshot getters: `get_implicit_references`, `get_system_prompt_suffix_fragments`, and `get_user_prompt_suffix_fragments`.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- Coding-agent backends now text-project multimodal user prompts into local file paths or URL placeholders while provider-backed executors pass `UserContent` natively.
|
|
19
|
+
- Tool results now retain `ToolOutcome` payloads through backend boundaries so multimodal-capable transports can render native content before falling back to JSON previews.
|
|
20
|
+
- Optimize Natural block runtime prompt
|
|
21
|
+
- Update dependencies
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
- Cleaned up staged multimodal prompt files when text projection fails before a projected request is returned.
|
|
25
|
+
|
|
26
|
+
## [0.10.0]
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
- Scope-level oversight hooks in `nh.scope(oversight=...)` for synchronous tool-call inspection and step-commit inspection.
|
|
30
|
+
- Oversight trace events and backend/wrapper coverage for accept/reject decision paths.
|
|
31
|
+
- Validation for `vote(min_success=...)` bounds (`>= 1` and `<= count`) with matching tests.
|
|
32
|
+
- Tool-boundary regression test ensuring recoverable `ToolBoundaryError` is wrapped into JSON payload.
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
- Finalized committed binding validation at step boundaries, added write-root dirty tracking for dotted `nh_assign`, and aligned prompt/docs with the new validation contract.
|
|
36
|
+
- Introduced `ExecutionRef` (`run_id`, `scope_id`, optional `step_id`) and renamed runtime accessor to `get_execution_ref`.
|
|
37
|
+
- Added top-level module access for `oversight` and `resilience`, with docs and public API tests aligned.
|
|
38
|
+
- Consolidated tool-call oversight inspection to a single wrapper-side entry point and removed duplicate inspection guard state.
|
|
39
|
+
- Unified tool result handling to TypedDict-based envelope types (`ToolResult`, `ToolError`) instead of Pydantic models.
|
|
40
|
+
- Clarified tool-boundary policy: recoverable tool-call failures are JSON-envelope responses, host invariant violations propagate as Python exceptions.
|
|
41
|
+
- Updated specification and runtime configuration docs to codify envelope-vs-raise boundary semantics for tool-call failures.
|
|
42
|
+
- Updated API and patterns docs for `ExecutionRef`, `get_execution_ref`, and oversight/resilience namespace exposure.
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
- Backend handler now preserves wrapped tool error payloads (`{"value": ..., "error": ...}`) without dropping error details.
|
|
46
|
+
- Tool-call context propagation now preserves step identity in execution ref during step execution.
|
|
47
|
+
|
|
48
|
+
## [0.9.0]
|
|
49
|
+
|
|
10
50
|
### Changed
|
|
51
|
+
|
|
11
52
|
- Redesigned `nh.scope()` around `mode` semantics (`"inherit"` default, `"replace"` for explicit replacement).
|
|
12
53
|
- Scope prompt suffix arguments now use list-based forms: `system_prompt_suffix_fragments` and `user_prompt_suffix_fragments`.
|
|
13
54
|
|
|
@@ -138,7 +179,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
138
179
|
- Step executor abstraction and provider integration foundation.
|
|
139
180
|
- Core documentation and project scaffolding.
|
|
140
181
|
|
|
141
|
-
[Unreleased]: https://github.com/kurusugawa-computer/nighthawk-python/compare/v0.
|
|
182
|
+
[Unreleased]: https://github.com/kurusugawa-computer/nighthawk-python/compare/v0.11.0...HEAD
|
|
183
|
+
[0.11.0]: https://github.com/kurusugawa-computer/nighthawk-python/compare/v0.10.0...v0.11.0
|
|
184
|
+
[0.10.0]: https://github.com/kurusugawa-computer/nighthawk-python/compare/v0.9.0...v0.10.0
|
|
185
|
+
[0.9.0]: https://github.com/kurusugawa-computer/nighthawk-python/compare/v0.8.0...v0.9.0
|
|
142
186
|
[0.8.0]: https://github.com/kurusugawa-computer/nighthawk-python/compare/v0.7.0...v0.8.0
|
|
143
187
|
[0.7.0]: https://github.com/kurusugawa-computer/nighthawk-python/compare/v0.6.1...v0.7.0
|
|
144
188
|
[0.6.1]: https://github.com/kurusugawa-computer/nighthawk-python/compare/v0.6.0...v0.6.1
|
|
@@ -81,6 +81,7 @@ PFOO="cd evals/promptfoo && PROMPTFOO_PYTHON=\"$(uv python find)\" npx promptfoo
|
|
|
81
81
|
|---|---|
|
|
82
82
|
| `eval $PFOO eval` | Full regression (all backends, all tests) |
|
|
83
83
|
| `eval $PFOO eval -c promptfooconfig-prompt-ab.yaml` | Prompt A/B test (gpt-5.4-mini only) |
|
|
84
|
+
| `eval $PFOO eval -c promptfooconfig-nano-sanity.yaml --no-cache` | Default-model sanity check (gpt-5.4-nano only) |
|
|
84
85
|
| `eval $PFOO eval -c promptfooconfig-agents.yaml` | Coding agent backends (reduced test set) |
|
|
85
86
|
| `eval $PFOO eval -c promptfooconfig.yaml --filter-pattern "P-BIND-001"` | Single test |
|
|
86
87
|
| `eval $PFOO eval -c promptfooconfig.yaml --filter-providers "claude-code-cli"` | Single backend |
|
|
@@ -90,6 +91,7 @@ PFOO="cd evals/promptfoo && PROMPTFOO_PYTHON=\"$(uv python find)\" npx promptfoo
|
|
|
90
91
|
|
|
91
92
|
- `promptfooconfig.yaml` — Regression: winner prompt combo across openai-responses, claude-code-cli, and codex backends. All test cases.
|
|
92
93
|
- `promptfooconfig-prompt-ab.yaml` — A/B testing: 4 prompt/tool variants on gpt-5.4-mini. All test cases.
|
|
94
|
+
- `promptfooconfig-nano-sanity.yaml` — Default-model sanity check on openai-responses:gpt-5.4-nano. All test cases.
|
|
93
95
|
- `promptfooconfig-agents.yaml` — Coding agent only: claude-code-cli and codex with reduced test set.
|
|
94
96
|
|
|
95
97
|
#### Directory layout
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nighthawk-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.11.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/
|
|
@@ -20,17 +20,17 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
20
20
|
Classifier: Typing :: Typed
|
|
21
21
|
Requires-Python: >=3.13
|
|
22
22
|
Requires-Dist: headson>=0.16.1
|
|
23
|
-
Requires-Dist: pydantic-ai-slim>=1.
|
|
24
|
-
Requires-Dist: pydantic>=2
|
|
25
|
-
Requires-Dist: pyyaml>=6
|
|
26
|
-
Requires-Dist: tenacity>=9
|
|
27
|
-
Requires-Dist: tiktoken>=0.12
|
|
23
|
+
Requires-Dist: pydantic-ai-slim>=1.87.0
|
|
24
|
+
Requires-Dist: pydantic>=2.13.3
|
|
25
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
26
|
+
Requires-Dist: tenacity>=9.1.4
|
|
27
|
+
Requires-Dist: tiktoken>=0.12.0
|
|
28
28
|
Provides-Extra: claude-code-cli
|
|
29
|
-
Requires-Dist: mcp>=1.
|
|
29
|
+
Requires-Dist: mcp>=1.27.0; extra == 'claude-code-cli'
|
|
30
30
|
Provides-Extra: claude-code-sdk
|
|
31
31
|
Requires-Dist: claude-agent-sdk==0.1.48; extra == 'claude-code-sdk'
|
|
32
32
|
Provides-Extra: codex
|
|
33
|
-
Requires-Dist: mcp>=1.
|
|
33
|
+
Requires-Dist: mcp>=1.27.0; extra == 'codex'
|
|
34
34
|
Description-Content-Type: text/markdown
|
|
35
35
|
|
|
36
36
|
[](https://pypi.org/project/nighthawk-python)
|
|
@@ -18,12 +18,17 @@
|
|
|
18
18
|
- StepContextLimits
|
|
19
19
|
- JsonableValue
|
|
20
20
|
- to_jsonable_value
|
|
21
|
-
-
|
|
21
|
+
- ExecutionRef
|
|
22
22
|
- get_current_step_context
|
|
23
23
|
- get_current_usage_meter
|
|
24
|
-
-
|
|
24
|
+
- get_execution_ref
|
|
25
|
+
- get_implicit_references
|
|
25
26
|
- get_step_executor
|
|
27
|
+
- get_system_prompt_suffix_fragments
|
|
28
|
+
- get_user_prompt_suffix_fragments
|
|
29
|
+
- resilience
|
|
26
30
|
- UsageMeter
|
|
31
|
+
- oversight
|
|
27
32
|
|
|
28
33
|
## Errors
|
|
29
34
|
|
|
@@ -123,4 +128,3 @@
|
|
|
123
128
|
## Testing
|
|
124
129
|
|
|
125
130
|
::: nighthawk.testing
|
|
126
|
-
|
|
@@ -38,6 +38,18 @@ All three backends provide capabilities not available with Pydantic AI providers
|
|
|
38
38
|
- **Callable discoverability:** Same rules as [Natural blocks](natural-blocks.md#functions-and-discoverability).
|
|
39
39
|
- **Project-scoped files:** The CLI loads its own project files (CLAUDE.md, AGENTS.md) from `working_directory`.
|
|
40
40
|
|
|
41
|
+
### Multimodal inputs
|
|
42
|
+
|
|
43
|
+
Coding agent backends do not receive native VLM-style user-prompt multimodal input. Nighthawk projects multimodal prompt content into the CLI text channel instead:
|
|
44
|
+
|
|
45
|
+
- `BinaryContent` is staged to a local file and referenced in the prompt with `<image>` / `<file>` plus a local file path.
|
|
46
|
+
- URL-based content (`ImageUrl`, `AudioUrl`, `DocumentUrl`, `VideoUrl`) is referenced by URL in the projected prompt text.
|
|
47
|
+
- `UploadedFile` user-prompt content is rejected because these backends cannot resolve provider-owned file identifiers. Tool results still preserve the rest of the payload and replace only the `UploadedFile` item with explanatory fallback text.
|
|
48
|
+
- Tool results cross the backend boundary as MCP content. Image (`BinaryContent.is_image`) and audio (`BinaryContent.is_audio`) items travel natively as `ImageContent` / `AudioContent`; all other `BinaryContent` and `FileUrl` items project to `TextContent` so the MCP path stays symmetric with the text-projected prompt path. When mixed top-level text/multimodal tool-result sequences preserve their original order they satisfy the same `UserContent` rule used for prompt hoisting. The user prompt itself remains text-projected for these backends.
|
|
49
|
+
- When a successful tool result splits to no text and no user content, the projected prompt stays header-only rather than inserting the preview JSON envelope.
|
|
50
|
+
|
|
51
|
+
This differs from provider-backed executors that can pass a `tuple[UserContent, ...]` to a multimodal-capable provider API directly.
|
|
52
|
+
|
|
41
53
|
### Working directory
|
|
42
54
|
|
|
43
55
|
All three backends accept `working_directory` in their model settings. This absolute path determines:
|
|
@@ -14,6 +14,7 @@ You are expected to write, review, and debug Python code that uses Nighthawk as
|
|
|
14
14
|
- Prefer explicit, typed write bindings. Runtime inference exists for unannotated write bindings, but new code should not rely on it.
|
|
15
15
|
- Keep outputs narrow and typed, especially when using coding agent backends. A block may do broad internal work, but the Python boundary should stay small.
|
|
16
16
|
- When prompt context is truncated and you see `<snipped>`, first reduce context surface or split the block. Increase `StepContextLimits` only after simplification fails.
|
|
17
|
+
- Explicit dotted multimodal references such as `<holder.photo>` can still be omitted under tight `locals_max_items` / `locals_max_tokens`; resize budgets rather than assuming hoisting is unconditional.
|
|
17
18
|
|
|
18
19
|
## 2. First decision: should this be Natural at all?
|
|
19
20
|
|
|
@@ -327,7 +328,7 @@ Async rule:
|
|
|
327
328
|
Resilience rule:
|
|
328
329
|
|
|
329
330
|
- Keep retry, fallback, timeout, budget, and circuit-breaker policy in Python, not inside Natural text.
|
|
330
|
-
- Import from `nighthawk.resilience`
|
|
331
|
+
- Import primitives from `nighthawk.resilience`. After `import nighthawk as nh`, the module namespace is also available as `nh.resilience`:
|
|
331
332
|
|
|
332
333
|
```py
|
|
333
334
|
from nighthawk.resilience import retrying
|
|
@@ -63,6 +63,7 @@ Three sections:
|
|
|
63
63
|
|
|
64
64
|
- **`<<<NH:PROGRAM>>>`** -- your Natural block text (after sentinel removal and `textwrap.dedent`).
|
|
65
65
|
- **`<<<NH:LOCALS>>>`** -- step locals, rendered alphabetically as `name: type = value`.
|
|
66
|
+
Nighthawk renders the full current locals snapshot by design; control context size by deciding which locals exist before the block.
|
|
66
67
|
- **`<<<NH:GLOBALS>>>`** -- module-level names referenced via `<name>` that are not in step locals.
|
|
67
68
|
|
|
68
69
|
### One block, one task
|
|
@@ -129,7 +130,7 @@ def detect_language(text: str):
|
|
|
129
130
|
return language
|
|
130
131
|
```
|
|
131
132
|
|
|
132
|
-
Type annotations on write bindings enable validation and coercion at
|
|
133
|
+
Type annotations on write bindings enable validation and coercion at step finalization for values that are actually committed.
|
|
133
134
|
|
|
134
135
|
### Pydantic model write bindings
|
|
135
136
|
|
|
@@ -161,6 +162,17 @@ When the LLM assigns a value to `verdict`, Nighthawk validates and coerces it to
|
|
|
161
162
|
|
|
162
163
|
Read and write bindings are rendered identically in the LOCALS section (e.g., `name: type = value`). The `<name>` vs `<:name>` distinction in the Natural program text is the signal that tells the LLM which names it may update. At runtime, Nighthawk enforces the distinction: read bindings block rebinding, while write bindings allow rebinding and commit values back to Python locals.
|
|
163
164
|
|
|
165
|
+
### Multimodal bindings
|
|
166
|
+
|
|
167
|
+
A binding may hold a Pydantic AI multimodal value -- `BinaryContent`, `ImageUrl`, `AudioUrl`, `DocumentUrl`, `VideoUrl`, or `UploadedFile`. These are first-class binding values: reference them with `<name>` like any other binding, and Nighthawk renders the inline placeholder (`<image>` for image media, `<file>` otherwise) in the LOCALS or GLOBALS line. Explicit dotted leaf references use the same mechanism, and dotted `list` / `tuple` leaves are hoisted when they satisfy the same `UserContent` rule as top-level bindings. `UploadedFile` is only usable as native user-prompt content on provider-backed executors that can resolve the provider-owned file reference; coding-agent backends reject it at the user-prompt boundary.
|
|
168
|
+
|
|
169
|
+
Transport depends on the executor:
|
|
170
|
+
|
|
171
|
+
- Provider-backed executors that accept Pydantic AI `UserContent` send the value natively to the VLM API.
|
|
172
|
+
- Coding-agent backends text-project the value (staged local files or URL references). See [Coding agent backends: multimodal inputs](coding-agent-backends.md#multimodal-inputs).
|
|
173
|
+
|
|
174
|
+
For the normative rendering and transport rules, including dotted-reference hoisting and the remaining v0.11.0 nesting restrictions, see [Specification Section 8.2](specification.md#82-locals-summary).
|
|
175
|
+
|
|
164
176
|
### f-string injection
|
|
165
177
|
|
|
166
178
|
Inline f-string blocks embed Python expressions directly into the Natural program text. The expression is evaluated when Python evaluates the f-string -- before the LLM sees the prompt.
|
|
@@ -323,6 +335,8 @@ python_average: (numbers)
|
|
|
323
335
|
|
|
324
336
|
Function parameters and local variables appear in LOCALS. Module-level names referenced via `<name>` that are _not_ in locals appear in GLOBALS. Nighthawk renders callable entries with their full signature and docstring intent -- but only when type information is available.
|
|
325
337
|
|
|
338
|
+
This is a deliberate tradeoff: LOCALS stays a flat, predictable snapshot of what Python has already bound. If you want less prompt context, keep fewer locals alive at the block boundary. Section truncation still follows the normal lexicographic rendering order, so under tight budgets an explicitly referenced dotted multimodal leaf can still be omitted along with any other later entry.
|
|
339
|
+
|
|
326
340
|
When you pass a module-level callable as a function parameter with a generic type (`object`, `Any`, or no annotation), the name moves from GLOBALS to LOCALS and **its signature is erased**. The LLM cannot discover the correct arguments or return type.
|
|
327
341
|
|
|
328
342
|
The principle extends beyond callables. Any module-level name that is stable across invocations -- constants, classes, utility functions -- should stay in GLOBALS via `<name>` read bindings rather than being pulled into LOCALS via parameters or local assignments. Reserve function parameters for data that genuinely varies per call.
|
|
@@ -326,7 +326,8 @@ Natural blocks are non-deterministic by nature. Production deployments need stra
|
|
|
326
326
|
from nighthawk.resilience import retrying, fallback, vote, timeout, budget, circuit_breaker
|
|
327
327
|
```
|
|
328
328
|
|
|
329
|
-
Import directly from `nighthawk.resilience`.
|
|
329
|
+
Import directly from `nighthawk.resilience`.
|
|
330
|
+
After `import nighthawk as nh`, the module namespace is also available as `nh.resilience`.
|
|
330
331
|
|
|
331
332
|
### Retrying
|
|
332
333
|
|
|
@@ -23,7 +23,7 @@ calculate_average([1, "2", "three", "cuatro", "五"]) # 3.0
|
|
|
23
23
|
|
|
24
24
|
Binding functions like `<python_average>` appear in the prompt as a compact signature line. The LLM's pre-trained Python knowledge lets it reason about types, return values, and composition from the signature alone, without JSON Schema or protocol overhead. See [Tool exposure efficiency](#tool-exposure-efficiency) for the quantitative comparison with MCP and CLI tool exposure.
|
|
25
25
|
|
|
26
|
-
With provider-backed executors, each Natural block is a single LLM call. A sentiment classifier whose write binding is typed as `Literal["positive", "negative", "neutral"]` rejects any output outside the declared set -- Pydantic validates
|
|
26
|
+
With provider-backed executors, each Natural block is a single LLM call. A sentiment classifier whose write binding is typed as `Literal["positive", "negative", "neutral"]` rejects any output outside the declared set -- Pydantic validates committed write bindings at step finalization, not as a hint. The same mechanism applies to numeric extraction (`int`, `float`), structured parsing (Pydantic models), and any task where the judgment space is bounded. Because the host program owns the loop, a misclassified result can be retried, logged, or routed to a fallback -- all in ordinary Python.
|
|
27
27
|
|
|
28
28
|
With [coding agent backends](coding-agent-backends.md), the same boundary contract applies, but each Natural block becomes an autonomous agent execution. The agent can read files, run commands, and invoke skills -- while typed bindings enforce what crosses the boundary back to Python. The same `scope()` and `run()` context managers that structure human-written workflows are equally legible to a coding agent constructing workflows programmatically. When a coding agent operates inside a Natural block, binding functions appear as Python signatures in the prompt:
|
|
29
29
|
|
|
@@ -35,6 +35,15 @@ with nh.run(step_executor):
|
|
|
35
35
|
# Add implicit global references for this scope
|
|
36
36
|
with nh.scope(implicit_references={"search_repository": search_repository}):
|
|
37
37
|
typed_labeling_step(ticket_text)
|
|
38
|
+
|
|
39
|
+
# Add synchronous oversight for this scope
|
|
40
|
+
with nh.scope(
|
|
41
|
+
oversight=nh.oversight.Oversight(
|
|
42
|
+
inspect_tool_call=inspect_tool_call,
|
|
43
|
+
inspect_step_commit=inspect_step_commit,
|
|
44
|
+
)
|
|
45
|
+
):
|
|
46
|
+
inspected_step(ticket_text)
|
|
38
47
|
```
|
|
39
48
|
|
|
40
49
|
Parameters:
|
|
@@ -42,6 +51,7 @@ Parameters:
|
|
|
42
51
|
- `mode`: scope composition mode. Default: `"inherit"`.
|
|
43
52
|
- `step_executor_configuration`: replace the entire configuration.
|
|
44
53
|
- `step_executor`: replace the step executor entirely.
|
|
54
|
+
- `oversight`: scope-level synchronous tool-call inspection and step-commit inspection hooks.
|
|
45
55
|
- `system_prompt_suffix_fragments`: scope-level system suffix fragments.
|
|
46
56
|
- `user_prompt_suffix_fragments`: scope-level user suffix fragments.
|
|
47
57
|
- `implicit_references`: scope-level implicit global references.
|
|
@@ -56,10 +66,16 @@ Mode semantics:
|
|
|
56
66
|
- Explicit `[]` or `{}` clears inherited list/dict values.
|
|
57
67
|
- Explicit list/dict values (for example `[e1, e2]` or `{k1: v1, k2: v2}`) fully replace inherited values.
|
|
58
68
|
|
|
69
|
+
For `oversight`, omitted means inherit the current hooks, while explicit `None` clears them for the nested scope.
|
|
70
|
+
|
|
59
71
|
The context manager yields the resolved `StepExecutor` for the scope.
|
|
60
72
|
|
|
61
73
|
`StepExecutorConfiguration` also accepts `system_prompt_suffix_fragments` and `user_prompt_suffix_fragments` (tuples of strings) as baseline suffix fragments for the whole run. In `mode="inherit"`, scope-level fragments are appended after configuration-level fragments.
|
|
62
74
|
|
|
75
|
+
System prompt suffix fragments are rendered with the same `$tool_result_max_tokens` placeholder used by built-in prompt templates. Use `$$tool_result_max_tokens` if the literal text must appear in the final prompt.
|
|
76
|
+
|
|
77
|
+
Text-projected backends that expose Nighthawk tools automatically add a short tool-result preview warning to the system prompt. You do not need to add a separate preview warning fragment for coding-agent or other text-projected tool transports.
|
|
78
|
+
|
|
63
79
|
## Scoped implicit references
|
|
64
80
|
|
|
65
81
|
`implicit_references` can inject global helper functions as step capabilities:
|
|
@@ -138,6 +154,59 @@ with nh.scope(mode="replace", system_prompt_suffix_fragments=None):
|
|
|
138
154
|
```
|
|
139
155
|
|
|
140
156
|
The same `replace` semantics apply to `user_prompt_suffix_fragments`.
|
|
157
|
+
|
|
158
|
+
## Reading the active scope
|
|
159
|
+
|
|
160
|
+
Within an active `nh.run()` context, three snapshot getters expose the current scope's accumulated state:
|
|
161
|
+
|
|
162
|
+
- `nh.get_implicit_references()` returns a `Mapping[str, object]` snapshot of implicit references.
|
|
163
|
+
- `nh.get_system_prompt_suffix_fragments()` returns the system prompt suffix fragments as a `tuple[str, ...]`.
|
|
164
|
+
- `nh.get_user_prompt_suffix_fragments()` returns the user prompt suffix fragments as a `tuple[str, ...]`.
|
|
165
|
+
|
|
166
|
+
The prompt suffix getters return only fragments accumulated via `nh.scope(...)`; configuration-level baseline fragments from `StepExecutorConfiguration` are not included.
|
|
167
|
+
|
|
168
|
+
Use these when composing nested scopes from a host helper: read the parent state, derive a subset, then re-enter via `nh.scope(mode="replace", ...)`. Both narrowing (keeping a subset) and masking (removing specific keys) are expressed by deriving a new mapping and passing it under `mode="replace"`.
|
|
169
|
+
|
|
170
|
+
```py
|
|
171
|
+
with nh.run(step_executor):
|
|
172
|
+
with nh.scope(implicit_references={"a": helper_a, "b": helper_b}):
|
|
173
|
+
current = nh.get_implicit_references()
|
|
174
|
+
narrowed = {name: value for name, value in current.items() if name != "b"}
|
|
175
|
+
with nh.scope(mode="replace", implicit_references=narrowed):
|
|
176
|
+
triage_issue(ticket_text)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
All three getters require an active run context. Outside `nh.run()` they raise `NighthawkError`, matching `nh.get_step_executor()` semantics. Catch `NighthawkError` if a helper needs to detect the absence of a run.
|
|
180
|
+
|
|
181
|
+
## Synchronous oversight in scopes
|
|
182
|
+
|
|
183
|
+
Use `nh.scope(oversight=...)` when the host needs synchronous inspection around tool calls or a final rewrite/reject checkpoint before Nighthawk commits a step result.
|
|
184
|
+
|
|
185
|
+
```py
|
|
186
|
+
def inspect_tool_call(tool_call: nh.oversight.ToolCall) -> nh.oversight.ToolCallDecision:
|
|
187
|
+
if tool_call.tool_name == "delete_file":
|
|
188
|
+
return nh.oversight.Reject("Deletion must be approved by a human.")
|
|
189
|
+
return nh.oversight.Accept()
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def inspect_step_commit(proposal: nh.oversight.StepCommitProposal) -> nh.oversight.StepCommitDecision:
|
|
193
|
+
if "result" in proposal.proposed_binding_name_to_value:
|
|
194
|
+
return nh.oversight.Rewrite(rewritten_binding_name_to_value={"result": "reviewed"})
|
|
195
|
+
return nh.oversight.Accept()
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
with nh.run(step_executor):
|
|
199
|
+
with nh.scope(
|
|
200
|
+
oversight=nh.oversight.Oversight(
|
|
201
|
+
inspect_tool_call=inspect_tool_call,
|
|
202
|
+
inspect_step_commit=inspect_step_commit,
|
|
203
|
+
)
|
|
204
|
+
):
|
|
205
|
+
inspected_step(ticket_text)
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Tool rejections are returned to the model as a recoverable observation. On preview-based paths this appears with `error.kind == "oversight"` in the projected preview; provider-backed paths that use Pydantic AI's standard retry loop may instead surface the same structured details as a retry prompt whose final line is compact JSON. Step rejections raise `nh.oversight.OversightRejectedError` to the host. Rewrite values still flow through the normal step finalization path. For the normative boundary rule on which tool-call failures are projected back to the model versus propagated as host exceptions, see [Specification Section 8.3](specification.md#83-tool-boundary-contract-built-in-tooling).
|
|
209
|
+
|
|
141
210
|
## Mixing executors
|
|
142
211
|
|
|
143
212
|
Use `nh.scope(step_executor=...)` to switch executors within a single run. This is the standard pattern for mixing a cheap classifier with a deep autonomous step:
|
|
@@ -181,12 +250,13 @@ See [Specification Section 8.2](specification.md#82-prompt-context) for the full
|
|
|
181
250
|
|
|
182
251
|
## Runtime execution identity
|
|
183
252
|
|
|
184
|
-
Each `nh.run()` generates an `
|
|
253
|
+
Each `nh.run()` generates an `ExecutionRef` with a unique `run_id` (trace root) and `scope_id`. Nested `nh.scope()` calls generate new `scope_id` values while keeping the same `run_id`.
|
|
185
254
|
|
|
186
255
|
```py
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
256
|
+
execution_ref = nh.get_execution_ref()
|
|
257
|
+
execution_ref.run_id # trace root -- stable across nested scopes
|
|
258
|
+
execution_ref.scope_id # current scope -- changes with each nh.scope()
|
|
259
|
+
execution_ref.step_id # None outside active step execution
|
|
190
260
|
```
|
|
191
261
|
|
|
192
262
|
Use `run_id` to correlate distributed agent processes in logs and traces. Use `scope_id` to identify the current logical execution context. See [Specification Section 10](specification.md#10-runtime-scoping) for the full specification and [Verification: observability](verification.md#observability) for tracing integration.
|
|
@@ -88,7 +88,7 @@ The condensed coding agent guide (`for-coding-agents.md`) is a derivative docume
|
|
|
88
88
|
- `step_system_prompt_template`: system prompt template that defines the step execution protocol.
|
|
89
89
|
- `step_user_prompt_template`: full user prompt template including section delimiters.
|
|
90
90
|
- `context_limits`: limits for rendering dynamic context into the prompt.
|
|
91
|
-
- `json_renderer_style`: [headson](https://github.com/kantord/headson) rendering style used in prompt context and tool
|
|
91
|
+
- `json_renderer_style`: [headson](https://github.com/kantord/headson) rendering style used in prompt context and projected tool-result previews. Default: `"default"`. Available values: `"strict"` (valid JSON, no annotations), `"default"` (pseudo-JSON with omission markers like `…`), `"detailed"` (JS-like with inline comments such as `// N more`).
|
|
92
92
|
- `system_prompt_suffix_fragments`: optional baseline system prompt suffix fragments for this executor configuration.
|
|
93
93
|
- `user_prompt_suffix_fragments`: optional baseline user prompt suffix fragments for this executor configuration.
|
|
94
94
|
|
|
@@ -103,17 +103,18 @@ The condensed coding agent guide (`for-coding-agents.md`) is a derivative docume
|
|
|
103
103
|
- Fields: `locals_max_tokens`, `locals_max_items`, `globals_max_tokens`, `globals_max_items`, `value_max_tokens`, `object_max_methods`, `object_max_fields`, `object_field_value_max_tokens`, `tool_result_max_tokens`.
|
|
104
104
|
- `JsonableValue`
|
|
105
105
|
- Type alias for JSON-serializable Python values (`dict | list | str | int | float | bool | None`).
|
|
106
|
-
- `
|
|
106
|
+
- `ExecutionRef`
|
|
107
107
|
- Frozen dataclass representing runtime execution identity.
|
|
108
108
|
- `run_id`: the Id of the outermost run (trace root).
|
|
109
109
|
- `scope_id`: the Id of the current scope.
|
|
110
|
+
- `step_id`: the Id of the current step when available, otherwise `None`.
|
|
110
111
|
|
|
111
112
|
### 5.4. Runtime accessors
|
|
112
113
|
|
|
113
114
|
- `nighthawk.get_current_step_context() -> StepContext`
|
|
114
115
|
- Get the `StepContext` for the currently executing Natural block. Raises if no step is active.
|
|
115
116
|
|
|
116
|
-
See [Section 10](#10-runtime-scoping) for additional runtime accessors (`get_step_executor`, `
|
|
117
|
+
See [Section 10](#10-runtime-scoping) for additional runtime accessors (`get_step_executor`, `get_execution_ref`).
|
|
117
118
|
|
|
118
119
|
## 6. Natural block detection
|
|
119
120
|
|
|
@@ -239,7 +240,7 @@ The template uses `$program`, `$locals`, and `$globals` placeholders, substitute
|
|
|
239
240
|
|
|
240
241
|
#### 8.2.2. Locals summary
|
|
241
242
|
|
|
242
|
-
The locals summary renders selected names from `step_locals`.
|
|
243
|
+
The locals summary renders selected names from `step_locals`. It is a transparent projection of Python-visible local state at the Natural block boundary. Nighthawk MUST NOT automatically suppress otherwise eligible LOCALS entries based on reference frequency, VLM cost, or multimodal payload type; users control prompt cost by reducing the Python locals that exist at the block boundary.
|
|
243
244
|
|
|
244
245
|
Selection:
|
|
245
246
|
|
|
@@ -268,6 +269,19 @@ Rendering format:
|
|
|
268
269
|
- Method expansion is bounded by `context_limits.object_max_methods`; field expansion is bounded by `context_limits.object_max_fields`; each field value preview is bounded by `context_limits.object_field_value_max_tokens`.
|
|
269
270
|
- If object member limits are exceeded, explicit omission lines are added: `name.<methods>: <snipped N public methods>` and/or `name.<fields>: <snipped N public fields>`.
|
|
270
271
|
- Other non-callable values: `name: type_name = json_value`, where `json_value` is bounded by `context_limits.value_max_tokens`.
|
|
272
|
+
- Top-level Pydantic AI multimodal values are rendered as `name: TypeName = ` followed by inline user content in the prompt payload.
|
|
273
|
+
- This applies to top-level locals/globals entries.
|
|
274
|
+
- Top-level `list` / `tuple` values are also rendered as inline user content when every item is valid Pydantic AI `UserContent` and at least one item is multimodal.
|
|
275
|
+
- The binding line still renders once, for example `photos: list = `, followed by the original top-level item order.
|
|
276
|
+
- Pure text-only sequences do not use this rule; they remain preview-rendered values.
|
|
277
|
+
- Explicit dotted references whose resolved leaf resolves to hoistable inline user content add a separate section line for that full dotted path (for example `holder.photo: BinaryContent = ` followed by inline user content).
|
|
278
|
+
- Only explicitly referenced dotted paths are added this way.
|
|
279
|
+
- This is a leaf-only rendering rule, not recursive object-graph extraction.
|
|
280
|
+
- Dotted-path resolution is attribute-oriented only. Each segment after the top-level name is resolved against discovered object fields using the same safe field sources as object capability rendering.
|
|
281
|
+
- Dotted-path resolution does not treat `Mapping` keys as attribute segments. For mapping access, use Python expressions or helper functions (for example `nh_eval("payload['photo']")`) rather than `<payload.photo>`.
|
|
282
|
+
- `list` / `tuple` leaves follow the same hoisting rule as top-level bindings: the sequence is hoisted when every item is valid Pydantic AI `UserContent` and at least one item is multimodal.
|
|
283
|
+
- Non-multimodal dotted references remain preview text inside object capability rendering.
|
|
284
|
+
- When a dotted multimodal reference is rendered as a separate section line, the corresponding field is omitted from the owning object capability block to avoid duplication. The field still consumes a slot in the `object_max_fields` budget.
|
|
271
285
|
|
|
272
286
|
Callable disambiguation considers both top-level callable entries and object method entries in the same section.
|
|
273
287
|
|
|
@@ -289,6 +303,7 @@ Token budgeting:
|
|
|
289
303
|
- Section budgets remain governed by `locals_max_tokens` / `globals_max_tokens` and item budgets by `locals_max_items` / `globals_max_items`.
|
|
290
304
|
- Object method and field expansion is additionally governed by object-specific limits listed above.
|
|
291
305
|
- Line-level token counting includes newline separators during budget checks.
|
|
306
|
+
- Each rendered multimodal content item is charged a fixed internal budgeting heuristic against the section budget so that multimodal-heavy sections still trigger truncation. This is not a provider-side token estimate.
|
|
292
307
|
|
|
293
308
|
Observability:
|
|
294
309
|
|
|
@@ -299,6 +314,7 @@ Truncation:
|
|
|
299
314
|
|
|
300
315
|
- Rendering is bounded by `context_limits.locals_max_tokens` and `context_limits.locals_max_items`.
|
|
301
316
|
- When the limit is reached before all entries are rendered, a `<snipped>` marker is appended and a diagnostic log message is emitted on the `nighthawk` logger.
|
|
317
|
+
- Because section rendering remains lexicographic, tight item or token budgets can affect which bindings remain visible, including explicitly referenced dotted multimodal leaves.
|
|
302
318
|
|
|
303
319
|
#### 8.2.3. Globals summary
|
|
304
320
|
|
|
@@ -307,7 +323,7 @@ The globals summary renders module-level names that are referenced in the Natura
|
|
|
307
323
|
Reference extraction:
|
|
308
324
|
|
|
309
325
|
- The Natural program text is scanned for unescaped `<name>` tokens (both read bindings `<name>` and dotted references `<name.field>`).
|
|
310
|
-
- For dotted references,
|
|
326
|
+
- For dotted references, the top-level name (before the first `.`) participates in globals selection, and the full dotted path remains available for multimodal leaf rendering.
|
|
311
327
|
- Escaped references (`\<name>`) are not extracted. The backslash is removed in the program text passed to the model.
|
|
312
328
|
|
|
313
329
|
Selection:
|
|
@@ -392,6 +408,7 @@ Eval tool:
|
|
|
392
408
|
|
|
393
409
|
- `nh_eval(expression: str) -> object`
|
|
394
410
|
- Evaluate a Python expression and return the result. Use to inspect values, call functions, and mutate objects in-place.
|
|
411
|
+
- In-place mutations performed via `nh_eval` are not runtime-validated.
|
|
395
412
|
- If the evaluated expression is awaitable, it is awaited before returning.
|
|
396
413
|
|
|
397
414
|
Binding tool:
|
|
@@ -421,13 +438,16 @@ Semantics of `nh_assign`:
|
|
|
421
438
|
- Traverse attributes for each intermediate segment.
|
|
422
439
|
- Assign using attribute assignment on the final segment.
|
|
423
440
|
- Validation:
|
|
424
|
-
- Validate only when runtime type metadata is available; otherwise assign without validation.
|
|
441
|
+
- Validate only the final assigned field when runtime type metadata is available; otherwise assign without validation.
|
|
425
442
|
|
|
426
443
|
Commit and mutation notes:
|
|
427
444
|
|
|
428
445
|
- Commit selection is controlled only by `<:name>` bindings.
|
|
429
446
|
- `<:name>` selects which top-level names are committed from `step_locals` into Python locals at Natural block boundaries.
|
|
430
|
-
- Dotted
|
|
447
|
+
- Dotted `nh_assign` on a write binding root marks that root as dirty for commit selection.
|
|
448
|
+
- Dotted `nh_assign` on a read binding root does not participate in commit selection.
|
|
449
|
+
- Final validation is applied only to committed write bindings at step finalization.
|
|
450
|
+
- Dotted mutation on read bindings and in-place mutation via `nh_eval` are outside the final validation guarantee.
|
|
431
451
|
|
|
432
452
|
Write tool return value:
|
|
433
453
|
|
|
@@ -437,16 +457,47 @@ Write tool return value:
|
|
|
437
457
|
- validation details (when relevant)
|
|
438
458
|
- error details (on failure)
|
|
439
459
|
|
|
440
|
-
Tool result
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
-
|
|
445
|
-
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
460
|
+
Tool result transport contract:
|
|
461
|
+
|
|
462
|
+
- Internally, tool execution produces a canonical `ToolOutcome` with:
|
|
463
|
+
- `payload`: the success payload, which may include native Pydantic AI multimodal values
|
|
464
|
+
- `error`: `null` on success or a structured error object on failure
|
|
465
|
+
- The host also derives a projected preview observation for text-only transports, logging, and tracing.
|
|
466
|
+
- This projection may be derived lazily at the transport or tracing boundary rather than eagerly at tool execution time.
|
|
467
|
+
- Text-projected backends that expose Nighthawk tools automatically add system-prompt guidance that tool-result previews may be lossy and are bounded by `context_limits.tool_result_max_tokens`.
|
|
468
|
+
- The projected preview uses the following JSON shape:
|
|
469
|
+
- Success: `{"value": <bounded JSON rendering>, "error": null}`
|
|
470
|
+
- Failure: `{"value": null, "error": {"kind": "<category>", "message": "<detail>", "guidance": "<recovery hint>"}}`
|
|
471
|
+
- Multimodal-capable transports MAY send top-level multimodal payload items natively instead of embedding them into the preview text.
|
|
472
|
+
- When they do, they preserve the original top-level content item order from the tool payload, including text/media adjacency for qualifying mixed `list` / `tuple` payloads.
|
|
473
|
+
- In v0.11.0, the MCP tool-return transport carries only image (`BinaryContent.is_image`) and audio (`BinaryContent.is_audio`) items natively (as `mcp.types.ImageContent` / `mcp.types.AudioContent`). All other `BinaryContent` and `FileUrl` items, including document and video, project to `mcp.types.TextContent` so the transport stays symmetric with text-projected backends. Future expansion of native carriage to document or video items via `mcp.types.EmbeddedResource` is intentionally out of scope in v0.11.0 and is not guaranteed by this specification. Multimodal-capable providers that bypass MCP still send those items natively via `ToolReturnPart.files`.
|
|
474
|
+
- Provider-backed backends that use Pydantic AI's standard tool loop SHOULD surface general `ToolOutcome.error` failures as ordinary tool results rather than retry prompts.
|
|
475
|
+
- In v0.11.0, the dedicated retry-prompt path is reserved for tool argument validation failures before tool execution (for example via `RetryPromptPart.model_response()`).
|
|
476
|
+
- When a provider-backed backend surfaces a tool failure without native multimodal content, it SHOULD use the same structured preview envelope shape described above.
|
|
477
|
+
- User-prompt transport is backend-dependent.
|
|
478
|
+
- Provider-backed executors that accept Pydantic AI `UserContent` MAY send user-prompt multimodal content natively.
|
|
479
|
+
- Text-projected backends (including coding agent backends that must talk to a CLI text channel) project multimodal user prompt content to text placeholders plus local file paths or URL references instead of sending native VLM input.
|
|
480
|
+
- For tool results, backends SHOULD preserve top-level mixed text/multimodal order when the payload is a `list` / `tuple` whose items are all valid `UserContent` and at least one item is multimodal.
|
|
481
|
+
- When that ordered-segmentation rule does not apply, text-projected transports SHOULD derive tool-return text from Pydantic AI's `ToolReturnPart.model_response_str_and_user_content()` and project the returned `user_content` separately.
|
|
482
|
+
- MCP transports SHOULD use the same fallback split when ordered segmentation does not apply: one text observation derived from `tool_response_text`, followed by the extracted `user_content` items in order.
|
|
483
|
+
- If a rich transport fails while projecting an otherwise recoverable tool result, the backend SHOULD degrade to the projected preview text rather than fail the step.
|
|
484
|
+
- When Pydantic AI reports a successful empty split (`tool_response_text == ""` and extracted `user_content` is empty), backends MUST preserve that as an empty success rather than falling back to the projected preview envelope.
|
|
485
|
+
- Text-projected transports render only their normal tool-return framing with no preview JSON body.
|
|
486
|
+
- MCP-style native transports return an empty content list for that tool result.
|
|
487
|
+
- `BinaryContent` values can be staged to local files for text-projected transports because the host already has the bytes.
|
|
488
|
+
- URL-based values such as `ImageUrl` / `FileUrl` MAY be transported natively by multimodal-capable provider backends, but text-projected backends and the MCP tool-return transport treat them as URL references only.
|
|
489
|
+
- In v0.11.0, nested multimodal values inside dict/model/object structure are not hoisted; they remain part of the preview rendering only, except for top-level or explicitly referenced dotted-leaf `list` / `tuple` bindings whose items are valid `UserContent`.
|
|
490
|
+
- `UploadedFile` values are provider-owned references. Backends that cannot resolve the provider file identifier MUST reject them at the user-prompt boundary.
|
|
491
|
+
- For tool results, backends that cannot resolve an `UploadedFile` MUST preserve the rest of the top-level payload and replace only that item with an explanatory text fallback instead of silently dropping it.
|
|
492
|
+
|
|
493
|
+
Error kind categories: `invalid_input`, `resolution`, `execution`, `transient`, `internal`, `oversight`.
|
|
494
|
+
|
|
495
|
+
Boundary rule for tool-call failures:
|
|
496
|
+
|
|
497
|
+
- If the model can recover by changing tool arguments, choosing another tool, or continuing without the tool, the failure MUST be projected back to the model as a structured error observation.
|
|
498
|
+
- Host invariant violations (for example, broken runtime contract or invalid host-side oversight hook contract) MAY propagate as Python exceptions to the host instead of being projected.
|
|
499
|
+
|
|
500
|
+
The projected `value` field is bounded by `context_limits.tool_result_max_tokens` and may be summarized using [headson](https://github.com/kantord/headson) truncation when the full rendering exceeds the token budget. Headson is a structure-aware JSON summarizer: it parses the full JSON tree, then selects representative nodes to produce a compact preview that preserves the shape and key values of the data within a strict byte budget (analogous to `head`/`tail` but for structured data).
|
|
450
501
|
|
|
451
502
|
Atomicity requirement:
|
|
452
503
|
|
|
@@ -570,10 +621,11 @@ The required runtime object for step execution is:
|
|
|
570
621
|
|
|
571
622
|
- `step_executor` (required): a strategy object responsible for executing steps (Natural blocks).
|
|
572
623
|
|
|
573
|
-
Runtime execution identity is modeled separately in `
|
|
624
|
+
Runtime execution identity is modeled separately in `ExecutionRef`:
|
|
574
625
|
|
|
575
626
|
- `run_id`: the Id of the outermost run (trace root). This serves as the golden thread that connects distributed agent processes (e.g. parent, child, grandchild) across process boundaries in observability tools.
|
|
576
627
|
- `scope_id`: the Id of the current (possibly nested) run scope. This serves as the identity of the current logical execution context.
|
|
628
|
+
- `step_id`: the Id of the current step when available. Outside active step execution it is `None`.
|
|
577
629
|
|
|
578
630
|
Nighthawk does not own workspace filesystem concerns (such as include resolution or host file operations). Those concerns belong to the host application layer that embeds Nighthawk.
|
|
579
631
|
|
|
@@ -582,14 +634,15 @@ API:
|
|
|
582
634
|
|
|
583
635
|
- `nighthawk.run(step_executor: StepExecutor, *, run_id: str | None = None)`
|
|
584
636
|
- Replaces the current context step executor with the provided step executor.
|
|
585
|
-
- Generates a new `
|
|
637
|
+
- Generates a new `ExecutionRef` for the duration of the `with`.
|
|
586
638
|
- Uses provided `run_id` when given; otherwise generates a new `run_id` (trace root).
|
|
587
639
|
- Always generates a fresh `scope_id`.
|
|
588
640
|
- Can be used even when no step executor is currently set.
|
|
589
|
-
- `nighthawk.scope(*, mode: Literal["inherit", "replace"] = "inherit", step_executor_configuration: StepExecutorConfiguration | None = None, step_executor: StepExecutor | None = None, system_prompt_suffix_fragments: Sequence[str] | None = None, user_prompt_suffix_fragments: Sequence[str] | None = None, implicit_references: Mapping[str, object] | None = None) -> Iterator[StepExecutor]`
|
|
641
|
+
- `nighthawk.scope(*, mode: Literal["inherit", "replace"] = "inherit", step_executor_configuration: StepExecutorConfiguration | None = None, step_executor: StepExecutor | None = None, oversight: Oversight | None = None, system_prompt_suffix_fragments: Sequence[str] | None = None, user_prompt_suffix_fragments: Sequence[str] | None = None, implicit_references: Mapping[str, object] | None = None) -> Iterator[StepExecutor]`
|
|
590
642
|
- Enter a nested scope within the current run.
|
|
591
643
|
- Requires an existing step executor.
|
|
592
644
|
- Generates a new `scope_id` (keeps the current `run_id`).
|
|
645
|
+
- `oversight` omitted means inherit the current hooks; explicit `None` clears them for the nested scope.
|
|
593
646
|
- `mode="inherit"` (default):
|
|
594
647
|
- Appends prompt suffix fragment lists.
|
|
595
648
|
- Merges `implicit_references` additively with conflict checks.
|
|
@@ -601,7 +654,7 @@ API:
|
|
|
601
654
|
- Yields the resolved `StepExecutor` for the scope.
|
|
602
655
|
- `nighthawk.get_step_executor() -> StepExecutor`
|
|
603
656
|
- Get the current step executor. Raises if unset.
|
|
604
|
-
- `nighthawk.
|
|
657
|
+
- `nighthawk.get_execution_ref() -> ExecutionRef`
|
|
605
658
|
- Get the current runtime execution identity. Raises if unset.
|
|
606
659
|
|
|
607
660
|
### 10.1. Observability contract (OpenTelemetry span/event)
|