nighthawk-python 0.8.0__tar.gz → 0.10.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.8.0 → nighthawk_python-0.10.0}/.github/workflows/docs.yml +1 -1
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/CHANGELOG.md +39 -1
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/PKG-INFO +1 -1
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/docs/api.md +4 -4
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/docs/for-coding-agents.md +7 -3
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/docs/natural-blocks.md +1 -1
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/docs/patterns.md +2 -1
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/docs/philosophy.md +1 -1
- nighthawk_python-0.10.0/docs/runtime-configuration.md +252 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/docs/specification.md +28 -14
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/pyproject.toml +1 -1
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/src/nighthawk/__init__.py +6 -5
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/src/nighthawk/backends/mcp_server.py +2 -1
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/src/nighthawk/backends/tool_bridge.py +12 -10
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/src/nighthawk/configuration.py +0 -41
- nighthawk_python-0.10.0/src/nighthawk/oversight.py +180 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/src/nighthawk/resilience/__init__.py +2 -3
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/src/nighthawk/resilience/_vote.py +6 -5
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/src/nighthawk/runtime/prompt.py +4 -4
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/src/nighthawk/runtime/runner.py +209 -78
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/src/nighthawk/runtime/scoping.py +104 -75
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/src/nighthawk/runtime/step_context.py +8 -1
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/src/nighthawk/runtime/step_executor.py +6 -6
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/src/nighthawk/testing.py +11 -16
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/src/nighthawk/tools/assignment.py +7 -4
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/src/nighthawk/tools/contracts.py +7 -8
- nighthawk_python-0.10.0/src/nighthawk/tools/execution.py +160 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/execution/stub_executor.py +2 -2
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/execution/test_natural_block_ordering.py +3 -3
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/execution/test_natural_traceback.py +2 -1
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/execution/test_runtime.py +201 -8
- nighthawk_python-0.10.0/tests/governance/test_governance.py +198 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/integration/test_llm_integration.py +2 -2
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/public/test_public_api.py +113 -8
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/resilience/test_vote.py +8 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/tools/test_assignment_async.py +37 -0
- nighthawk_python-0.10.0/tests/tools/test_tool_boundary.py +513 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/uv.lock +132 -132
- nighthawk_python-0.8.0/docs/runtime-configuration.md +0 -138
- nighthawk_python-0.8.0/src/nighthawk/tools/execution.py +0 -83
- nighthawk_python-0.8.0/tests/tools/test_tool_boundary.py +0 -218
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/.claude/rules/docs.md +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/.claude/rules/promptfoo.md +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/.claude/rules/src.md +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/.claude/rules/tests.md +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/.claude/settings.json +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/.claude/unset_envs.sh +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/.devcontainer/Dockerfile.devcontainer +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/.devcontainer/Dockerfile.litellm +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/.devcontainer/devcontainer.json +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/.devcontainer/docker-compose.yaml +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/.devcontainer/litellm-config.yaml +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/.github/dependabot.yml +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/.github/workflows/ci.yml +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/.github/workflows/publish.yml +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/.gitignore +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/.python-version +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/AGENTS.md +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/CLAUDE.md +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/CONTRIBUTING.md +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/LICENSE +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/README.md +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/docs/AGENTS.md +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/docs/assets/nighthawk_logo-128x128.png +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/docs/coding-agent-backends.md +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/docs/executors.md +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/docs/index.md +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/docs/pydantic-ai-providers.md +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/docs/quickstart.md +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/docs/roadmap.md +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/docs/verification.md +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/evals/promptfoo/assertions/__init__.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/evals/promptfoo/assertions/binding_value.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/evals/promptfoo/assertions/outcome_kind.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/evals/promptfoo/assertions/raise_message.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/evals/promptfoo/evidence/2026-03-26-baseline-prompt-ab.md +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/evals/promptfoo/evidence/2026-03-26-baseline-regression.md +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/evals/promptfoo/evidence/2026-03-26-baseline-suffix-ab.md +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/evals/promptfoo/promptfooconfig-agents.yaml +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/evals/promptfoo/promptfooconfig-prompt-ab.yaml +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/evals/promptfoo/promptfooconfig-suffix-ab.yaml +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/evals/promptfoo/promptfooconfig.yaml +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/evals/promptfoo/prompts/eval_coding_agent.txt +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/evals/promptfoo/prompts/eval_default.txt +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/evals/promptfoo/prompts/eval_mutation_aware.txt +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/evals/promptfoo/prompts/eval_sequenced.txt +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/evals/promptfoo/provider.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/evals/promptfoo/research-result.md +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/evals/promptfoo/test_cases/binding_operations.yaml +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/evals/promptfoo/test_cases/edge_cases.yaml +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/evals/promptfoo/test_cases/loop_outcomes.yaml +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/evals/promptfoo/test_cases/multi_step.yaml +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/evals/promptfoo/test_cases/null_handling.yaml +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/evals/promptfoo/test_cases/outcome_kinds.yaml +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/evals/promptfoo/test_cases/tool_selection.yaml +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/mkdocs.yml +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/pyrightconfig.json +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/src/AGENTS.md +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/src/nighthawk/backends/__init__.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/src/nighthawk/backends/base.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/src/nighthawk/backends/claude_code_cli.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/src/nighthawk/backends/claude_code_sdk.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/src/nighthawk/backends/claude_code_settings.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/src/nighthawk/backends/codex.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/src/nighthawk/backends/mcp_boundary.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/src/nighthawk/errors.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/src/nighthawk/identifier_path.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/src/nighthawk/json_renderer.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/src/nighthawk/natural/__init__.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/src/nighthawk/natural/blocks.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/src/nighthawk/natural/decorator.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/src/nighthawk/natural/transform.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/src/nighthawk/resilience/_budget.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/src/nighthawk/resilience/_circuit_breaker.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/src/nighthawk/resilience/_fallback.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/src/nighthawk/resilience/_retry.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/src/nighthawk/resilience/_timeout.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/src/nighthawk/runtime/__init__.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/src/nighthawk/runtime/async_bridge.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/src/nighthawk/runtime/step_contract.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/src/nighthawk/runtime/tool_calls.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/src/nighthawk/tools/__init__.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/src/nighthawk/tools/provided.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/src/nighthawk/tools/registry.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/src/nighthawk/ulid.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/AGENTS.md +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/__init__.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/backends/__init__.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/backends/test_claude_code_cli.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/backends/test_claude_code_sdk.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/backends/test_codex.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/conftest.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/docs/__init__.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/docs/test_coding_agent_examples.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/docs/test_docs_architecture.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/docs/test_prompt_examples.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/execution/__init__.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/execution/prompt_test_helpers.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/execution/test_execution_outcome_prompt_fragment.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/execution/test_globals_prompt.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/execution/test_infer_binding_types.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/execution/test_variables_prompt.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/integration/__init__.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/integration/skip_helpers.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/integration/test_carry_pattern.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/integration/test_claude_code_cli_integration.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/integration/test_claude_code_sdk_integration.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/integration/test_codex_integration.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/natural/__init__.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/natural/test_blocks.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/public/__init__.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/public/test_readme_example.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/public/test_usage_meter.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/resilience/__init__.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/resilience/test_budget.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/resilience/test_circuit_breaker.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/resilience/test_composition.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/resilience/test_fallback.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/resilience/test_retry.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/resilience/test_timeout.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/test_renderer.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/test_testing.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/tools/__init__.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/tools/test_contracts.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/tools/test_provided_async.py +0 -0
- {nighthawk_python-0.8.0 → nighthawk_python-0.10.0}/tests/tools/test_registry.py +0 -0
|
@@ -7,6 +7,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.10.0]
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Scope-level oversight hooks in `nh.scope(oversight=...)` for synchronous tool-call inspection and step-commit inspection.
|
|
14
|
+
- Oversight trace events and backend/wrapper coverage for accept/reject decision paths.
|
|
15
|
+
- Validation for `vote(min_success=...)` bounds (`>= 1` and `<= count`) with matching tests.
|
|
16
|
+
- Tool-boundary regression test ensuring recoverable `ToolBoundaryError` is wrapped into JSON payload.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- 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.
|
|
20
|
+
- Introduced `ExecutionRef` (`run_id`, `scope_id`, optional `step_id`) and renamed runtime accessor to `get_execution_ref`.
|
|
21
|
+
- Added top-level module access for `oversight` and `resilience`, with docs and public API tests aligned.
|
|
22
|
+
- Consolidated tool-call oversight inspection to a single wrapper-side entry point and removed duplicate inspection guard state.
|
|
23
|
+
- Unified tool result handling to TypedDict-based envelope types (`ToolResult`, `ToolError`) instead of Pydantic models.
|
|
24
|
+
- Clarified tool-boundary policy: recoverable tool-call failures are JSON-envelope responses, host invariant violations propagate as Python exceptions.
|
|
25
|
+
- Updated specification and runtime configuration docs to codify envelope-vs-raise boundary semantics for tool-call failures.
|
|
26
|
+
- Updated API and patterns docs for `ExecutionRef`, `get_execution_ref`, and oversight/resilience namespace exposure.
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
- Backend handler now preserves wrapped tool error payloads (`{"value": ..., "error": ...}`) without dropping error details.
|
|
30
|
+
- Tool-call context propagation now preserves step identity in execution ref during step execution.
|
|
31
|
+
|
|
32
|
+
## [0.9.0]
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- Redesigned `nh.scope()` around `mode` semantics (`"inherit"` default, `"replace"` for explicit replacement).
|
|
37
|
+
- Scope prompt suffix arguments now use list-based forms: `system_prompt_suffix_fragments` and `user_prompt_suffix_fragments`.
|
|
38
|
+
|
|
39
|
+
### Removed
|
|
40
|
+
- Removed `step_executor_configuration_patch` from `nh.scope()`.
|
|
41
|
+
- Removed `StepExecutorConfigurationPatch` from the public API.
|
|
42
|
+
|
|
43
|
+
### Fixed
|
|
44
|
+
- Clarified and aligned docs/tests for scope merge vs replace behavior.
|
|
45
|
+
|
|
10
46
|
## [0.8.0]
|
|
11
47
|
|
|
12
48
|
### Added
|
|
@@ -127,7 +163,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
127
163
|
- Step executor abstraction and provider integration foundation.
|
|
128
164
|
- Core documentation and project scaffolding.
|
|
129
165
|
|
|
130
|
-
[Unreleased]: https://github.com/kurusugawa-computer/nighthawk-python/compare/v0.
|
|
166
|
+
[Unreleased]: https://github.com/kurusugawa-computer/nighthawk-python/compare/v0.10.0...HEAD
|
|
167
|
+
[0.10.0]: https://github.com/kurusugawa-computer/nighthawk-python/compare/v0.9.0...v0.10.0
|
|
168
|
+
[0.9.0]: https://github.com/kurusugawa-computer/nighthawk-python/compare/v0.8.0...v0.9.0
|
|
131
169
|
[0.8.0]: https://github.com/kurusugawa-computer/nighthawk-python/compare/v0.7.0...v0.8.0
|
|
132
170
|
[0.7.0]: https://github.com/kurusugawa-computer/nighthawk-python/compare/v0.6.1...v0.7.0
|
|
133
171
|
[0.6.1]: https://github.com/kurusugawa-computer/nighthawk-python/compare/v0.6.0...v0.6.1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nighthawk-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.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/
|
|
@@ -14,17 +14,18 @@
|
|
|
14
14
|
- scope
|
|
15
15
|
- AgentStepExecutor
|
|
16
16
|
- StepExecutorConfiguration
|
|
17
|
-
- StepExecutorConfigurationPatch
|
|
18
17
|
- StepPromptTemplates
|
|
19
18
|
- StepContextLimits
|
|
20
19
|
- JsonableValue
|
|
21
20
|
- to_jsonable_value
|
|
22
|
-
-
|
|
21
|
+
- ExecutionRef
|
|
23
22
|
- get_current_step_context
|
|
24
23
|
- get_current_usage_meter
|
|
25
|
-
-
|
|
24
|
+
- get_execution_ref
|
|
26
25
|
- get_step_executor
|
|
26
|
+
- resilience
|
|
27
27
|
- UsageMeter
|
|
28
|
+
- oversight
|
|
28
29
|
|
|
29
30
|
## Errors
|
|
30
31
|
|
|
@@ -124,4 +125,3 @@
|
|
|
124
125
|
## Testing
|
|
125
126
|
|
|
126
127
|
::: nighthawk.testing
|
|
127
|
-
|
|
@@ -146,8 +146,12 @@ with nh.run(fast_executor):
|
|
|
146
146
|
report = write_analysis_report(ticket_text, product_summary)
|
|
147
147
|
```
|
|
148
148
|
|
|
149
|
-
`implicit_references`
|
|
150
|
-
|
|
149
|
+
- `implicit_references` inject helper functions as block capabilities.
|
|
150
|
+
- `nh.scope()` must run inside `nh.run()`; it keeps `run_id` and creates a new `scope_id`.
|
|
151
|
+
- `mode` affects only `system_prompt_suffix_fragments`, `user_prompt_suffix_fragments`, and `implicit_references`.
|
|
152
|
+
- `inherit`: suffix lists append; `implicit_references` merge with conflict check (same key + different object => `NighthawkError`).
|
|
153
|
+
- `replace`: explicit list/mapping fully replace; `[]`/`{}` clear; `None` keeps inherited value.
|
|
154
|
+
- `step_executor` / `step_executor_configuration` are resolved independently of `mode`.
|
|
151
155
|
|
|
152
156
|
## 4. The standard contract shape
|
|
153
157
|
|
|
@@ -323,7 +327,7 @@ Async rule:
|
|
|
323
327
|
Resilience rule:
|
|
324
328
|
|
|
325
329
|
- Keep retry, fallback, timeout, budget, and circuit-breaker policy in Python, not inside Natural text.
|
|
326
|
-
- Import from `nighthawk.resilience`
|
|
330
|
+
- Import primitives from `nighthawk.resilience`. After `import nighthawk as nh`, the module namespace is also available as `nh.resilience`:
|
|
327
331
|
|
|
328
332
|
```py
|
|
329
333
|
from nighthawk.resilience import retrying
|
|
@@ -129,7 +129,7 @@ def detect_language(text: str):
|
|
|
129
129
|
return language
|
|
130
130
|
```
|
|
131
131
|
|
|
132
|
-
Type annotations on write bindings enable validation and coercion at
|
|
132
|
+
Type annotations on write bindings enable validation and coercion at step finalization for values that are actually committed.
|
|
133
133
|
|
|
134
134
|
### Pydantic model write bindings
|
|
135
135
|
|
|
@@ -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
|
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# Runtime configuration
|
|
2
|
+
|
|
3
|
+
> This page assumes you have completed [Executors](executors.md).
|
|
4
|
+
|
|
5
|
+
This page covers how to configure execution at runtime: scoping modes, prompt suffix fragments, context limits, JSON rendering, and execution identity. These settings are independent of executor choice and apply equally to Pydantic AI providers and coding agent backends.
|
|
6
|
+
|
|
7
|
+
## Scoped overrides with `nh.scope()`
|
|
8
|
+
|
|
9
|
+
Use `nh.scope()` to override execution settings within an existing run. Each scope generates a new `scope_id` while keeping the current `run_id`.
|
|
10
|
+
|
|
11
|
+
```py
|
|
12
|
+
with nh.run(step_executor):
|
|
13
|
+
|
|
14
|
+
# Inherit mode (default): merge/append into current scope state
|
|
15
|
+
with nh.scope(
|
|
16
|
+
step_executor_configuration=nh.StepExecutorConfiguration(
|
|
17
|
+
model="openai-responses:gpt-5.4-mini",
|
|
18
|
+
),
|
|
19
|
+
) as scoped_executor:
|
|
20
|
+
expensive_analysis(data)
|
|
21
|
+
|
|
22
|
+
# Replace mode: replace only explicitly provided values
|
|
23
|
+
# None means "no change". [] / {} means "clear".
|
|
24
|
+
with nh.scope(
|
|
25
|
+
mode="replace",
|
|
26
|
+
system_prompt_suffix_fragments=["Always respond in formal English."],
|
|
27
|
+
implicit_references={},
|
|
28
|
+
):
|
|
29
|
+
formal_summary(text)
|
|
30
|
+
|
|
31
|
+
# Replace the step executor entirely for a section
|
|
32
|
+
with nh.scope(step_executor=another_executor):
|
|
33
|
+
specialized_step(data)
|
|
34
|
+
|
|
35
|
+
# Add implicit global references for this scope
|
|
36
|
+
with nh.scope(implicit_references={"search_repository": search_repository}):
|
|
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)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Parameters:
|
|
50
|
+
|
|
51
|
+
- `mode`: scope composition mode. Default: `"inherit"`.
|
|
52
|
+
- `step_executor_configuration`: replace the entire configuration.
|
|
53
|
+
- `step_executor`: replace the step executor entirely.
|
|
54
|
+
- `oversight`: scope-level synchronous tool-call inspection and step-commit inspection hooks.
|
|
55
|
+
- `system_prompt_suffix_fragments`: scope-level system suffix fragments.
|
|
56
|
+
- `user_prompt_suffix_fragments`: scope-level user suffix fragments.
|
|
57
|
+
- `implicit_references`: scope-level implicit global references.
|
|
58
|
+
|
|
59
|
+
Mode semantics:
|
|
60
|
+
|
|
61
|
+
- `mode="inherit"` (default):
|
|
62
|
+
- `system_prompt_suffix_fragments` and `user_prompt_suffix_fragments` are appended.
|
|
63
|
+
- `implicit_references` are merged additively with conflict checks.
|
|
64
|
+
- `mode="replace"`:
|
|
65
|
+
- `None` means no change.
|
|
66
|
+
- Explicit `[]` or `{}` clears inherited list/dict values.
|
|
67
|
+
- Explicit list/dict values (for example `[e1, e2]` or `{k1: v1, k2: v2}`) fully replace inherited values.
|
|
68
|
+
|
|
69
|
+
For `oversight`, omitted means inherit the current hooks, while explicit `None` clears them for the nested scope.
|
|
70
|
+
|
|
71
|
+
The context manager yields the resolved `StepExecutor` for the scope.
|
|
72
|
+
|
|
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.
|
|
74
|
+
|
|
75
|
+
## Scoped implicit references
|
|
76
|
+
|
|
77
|
+
`implicit_references` can inject global helper functions as step capabilities:
|
|
78
|
+
|
|
79
|
+
```py
|
|
80
|
+
def search_repository(query: str) -> list[str]: ...
|
|
81
|
+
|
|
82
|
+
with nh.run(step_executor):
|
|
83
|
+
with nh.scope(implicit_references={"search_repository": search_repository}):
|
|
84
|
+
triage_issue(ticket_text)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
In `mode="inherit"` (default), nested scopes merge additively with conflict checks.
|
|
88
|
+
In `mode="replace"`, explicit mappings fully replace inherited mappings.
|
|
89
|
+
|
|
90
|
+
```py
|
|
91
|
+
with nh.run(step_executor), nh.scope(implicit_references={"parent": search_repository}):
|
|
92
|
+
with nh.scope(mode="replace", implicit_references={}):
|
|
93
|
+
triage_issue(ticket_text)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The inner scope above clears inherited implicit references.
|
|
97
|
+
|
|
98
|
+
```py
|
|
99
|
+
with nh.scope(mode="replace", implicit_references={"child": search_repository}):
|
|
100
|
+
triage_issue(ticket_text)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
This fully replaces inherited references with the provided mapping.
|
|
104
|
+
|
|
105
|
+
```py
|
|
106
|
+
with nh.scope(mode="replace", implicit_references=None):
|
|
107
|
+
triage_issue(ticket_text)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
This keeps inherited references unchanged.
|
|
111
|
+
|
|
112
|
+
## Prompt suffix fragments in scopes
|
|
113
|
+
|
|
114
|
+
Use list values when setting scope-level suffix fragments:
|
|
115
|
+
|
|
116
|
+
```py
|
|
117
|
+
with nh.run(step_executor):
|
|
118
|
+
with nh.scope(system_prompt_suffix_fragments=["Use concise answers."]):
|
|
119
|
+
summarize_ticket(ticket_text)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`user_prompt_suffix_fragments` follows the same rules.
|
|
123
|
+
|
|
124
|
+
```py
|
|
125
|
+
with nh.scope(user_prompt_suffix_fragments=["Focus on actionable output."]):
|
|
126
|
+
summarize_ticket(ticket_text)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
In `mode="inherit"`, provided lists are appended.
|
|
130
|
+
In `mode="replace"`, provided lists fully replace inherited lists.
|
|
131
|
+
|
|
132
|
+
```py
|
|
133
|
+
with nh.run(step_executor), nh.scope(system_prompt_suffix_fragments=["parent"]):
|
|
134
|
+
with nh.scope(mode="replace", system_prompt_suffix_fragments=["child_1", "child_2"]):
|
|
135
|
+
summarize_ticket(ticket_text)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Pass `[]` to clear inherited fragments.
|
|
139
|
+
|
|
140
|
+
```py
|
|
141
|
+
with nh.scope(mode="replace", system_prompt_suffix_fragments=[]):
|
|
142
|
+
summarize_ticket(ticket_text)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Pass `None` to keep inherited fragments unchanged.
|
|
146
|
+
|
|
147
|
+
```py
|
|
148
|
+
with nh.scope(mode="replace", system_prompt_suffix_fragments=None):
|
|
149
|
+
summarize_ticket(ticket_text)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
The same `replace` semantics apply to `user_prompt_suffix_fragments`.
|
|
153
|
+
|
|
154
|
+
## Synchronous oversight in scopes
|
|
155
|
+
|
|
156
|
+
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.
|
|
157
|
+
|
|
158
|
+
```py
|
|
159
|
+
def inspect_tool_call(tool_call: nh.oversight.ToolCall) -> nh.oversight.ToolCallDecision:
|
|
160
|
+
if tool_call.tool_name == "delete_file":
|
|
161
|
+
return nh.oversight.Reject("Deletion must be approved by a human.")
|
|
162
|
+
return nh.oversight.Accept()
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def inspect_step_commit(proposal: nh.oversight.StepCommitProposal) -> nh.oversight.StepCommitDecision:
|
|
166
|
+
if "result" in proposal.proposed_binding_name_to_value:
|
|
167
|
+
return nh.oversight.Rewrite(rewritten_binding_name_to_value={"result": "reviewed"})
|
|
168
|
+
return nh.oversight.Accept()
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
with nh.run(step_executor):
|
|
172
|
+
with nh.scope(
|
|
173
|
+
oversight=nh.oversight.Oversight(
|
|
174
|
+
inspect_tool_call=inspect_tool_call,
|
|
175
|
+
inspect_step_commit=inspect_step_commit,
|
|
176
|
+
)
|
|
177
|
+
):
|
|
178
|
+
inspected_step(ticket_text)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Tool rejections are returned to the model as a normal tool result envelope with `error.kind == "oversight"`. 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 envelope-wrapped versus propagated as host exceptions, see [Specification Section 8.3](specification.md#83-tool-boundary-contract-built-in-tooling).
|
|
182
|
+
|
|
183
|
+
## Mixing executors
|
|
184
|
+
|
|
185
|
+
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:
|
|
186
|
+
|
|
187
|
+
```py
|
|
188
|
+
fast_executor = nh.AgentStepExecutor.from_configuration(
|
|
189
|
+
configuration=nh.StepExecutorConfiguration(model="openai-responses:gpt-5.4-mini"),
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
deep_executor = nh.AgentStepExecutor.from_configuration(
|
|
193
|
+
configuration=nh.StepExecutorConfiguration(model="codex:default"),
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
with nh.run(fast_executor):
|
|
197
|
+
label = classify_ticket(text) # fast, cheap
|
|
198
|
+
with nh.scope(step_executor=deep_executor):
|
|
199
|
+
diagnosis = inspect_repository(text) # deep, autonomous
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
See [Executors](executors.md#decision-tree) for when to choose a coding agent backend over a provider-backed executor.
|
|
203
|
+
|
|
204
|
+
## Context limits
|
|
205
|
+
|
|
206
|
+
The LOCALS and GLOBALS sections are bounded by token and item limits configured via `StepContextLimits`. When a limit is reached, remaining entries are omitted and a `<snipped>` marker is appended. The underlying data remains in Python memory and is accessible through binding functions at runtime -- truncation affects prompt coherence, not data availability.
|
|
207
|
+
|
|
208
|
+
```py
|
|
209
|
+
configuration = nh.StepExecutorConfiguration(
|
|
210
|
+
model="openai-responses:gpt-5.4-mini",
|
|
211
|
+
context_limits=nh.StepContextLimits(
|
|
212
|
+
locals_max_tokens=4096,
|
|
213
|
+
locals_max_items=50,
|
|
214
|
+
),
|
|
215
|
+
)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
See [Specification Section 8.2](specification.md#82-prompt-context) for the full specification.
|
|
219
|
+
|
|
220
|
+
## JSON rendering style
|
|
221
|
+
|
|
222
|
+
`StepExecutorConfiguration` also accepts `json_renderer_style`, which controls how values are rendered in prompt context and tool results (e.g., strict JSON vs annotated pseudo-JSON with omission markers). See [Specification Section 5.2](specification.md#52-configuration) for available styles.
|
|
223
|
+
|
|
224
|
+
## Runtime execution identity
|
|
225
|
+
|
|
226
|
+
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`.
|
|
227
|
+
|
|
228
|
+
```py
|
|
229
|
+
execution_ref = nh.get_execution_ref()
|
|
230
|
+
execution_ref.run_id # trace root -- stable across nested scopes
|
|
231
|
+
execution_ref.scope_id # current scope -- changes with each nh.scope()
|
|
232
|
+
execution_ref.step_id # None outside active step execution
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
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.
|
|
236
|
+
|
|
237
|
+
## Usage metering
|
|
238
|
+
|
|
239
|
+
Each `nh.run()` creates a `UsageMeter` that accumulates LLM token usage across all Natural block executions in the run. The meter is thread-safe and updated automatically after each step.
|
|
240
|
+
|
|
241
|
+
```py
|
|
242
|
+
meter = nh.get_current_usage_meter() # None outside nh.run()
|
|
243
|
+
if meter is not None:
|
|
244
|
+
meter.total_tokens # cumulative input + output tokens
|
|
245
|
+
meter.snapshot() # independent RunUsage copy of current totals
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
`get_current_usage_meter()` returns `None` outside an active `nh.run()` context. Use the meter to inspect cumulative cost at decision points -- for example, to choose a cheaper model mid-pipeline when spend is high. For automatic budget enforcement, see [Patterns: Budget](patterns.md#budget).
|
|
249
|
+
|
|
250
|
+
## Next steps
|
|
251
|
+
|
|
252
|
+
Continue to **[Patterns](patterns.md)** for outcomes, error handling, async, cross-block composition, resilience, and common mistakes.
|
|
@@ -91,9 +91,6 @@ The condensed coding agent guide (`for-coding-agents.md`) is a derivative docume
|
|
|
91
91
|
- `json_renderer_style`: [headson](https://github.com/kantord/headson) rendering style used in prompt context and tool result envelopes. 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
|
-
- `StepExecutorConfigurationPatch`
|
|
95
|
-
- Partial override object for scoped configuration updates.
|
|
96
|
-
- Supports patching model, model settings, templates, limits, renderer style, tokenizer encoding, and prompt suffix fragment tuples.
|
|
97
94
|
|
|
98
95
|
### 5.3. Supporting types
|
|
99
96
|
|
|
@@ -106,17 +103,18 @@ The condensed coding agent guide (`for-coding-agents.md`) is a derivative docume
|
|
|
106
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`.
|
|
107
104
|
- `JsonableValue`
|
|
108
105
|
- Type alias for JSON-serializable Python values (`dict | list | str | int | float | bool | None`).
|
|
109
|
-
- `
|
|
106
|
+
- `ExecutionRef`
|
|
110
107
|
- Frozen dataclass representing runtime execution identity.
|
|
111
108
|
- `run_id`: the Id of the outermost run (trace root).
|
|
112
109
|
- `scope_id`: the Id of the current scope.
|
|
110
|
+
- `step_id`: the Id of the current step when available, otherwise `None`.
|
|
113
111
|
|
|
114
112
|
### 5.4. Runtime accessors
|
|
115
113
|
|
|
116
114
|
- `nighthawk.get_current_step_context() -> StepContext`
|
|
117
115
|
- Get the `StepContext` for the currently executing Natural block. Raises if no step is active.
|
|
118
116
|
|
|
119
|
-
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`).
|
|
120
118
|
|
|
121
119
|
## 6. Natural block detection
|
|
122
120
|
|
|
@@ -395,6 +393,7 @@ Eval tool:
|
|
|
395
393
|
|
|
396
394
|
- `nh_eval(expression: str) -> object`
|
|
397
395
|
- Evaluate a Python expression and return the result. Use to inspect values, call functions, and mutate objects in-place.
|
|
396
|
+
- In-place mutations performed via `nh_eval` are not runtime-validated.
|
|
398
397
|
- If the evaluated expression is awaitable, it is awaited before returning.
|
|
399
398
|
|
|
400
399
|
Binding tool:
|
|
@@ -424,13 +423,16 @@ Semantics of `nh_assign`:
|
|
|
424
423
|
- Traverse attributes for each intermediate segment.
|
|
425
424
|
- Assign using attribute assignment on the final segment.
|
|
426
425
|
- Validation:
|
|
427
|
-
- Validate only when runtime type metadata is available; otherwise assign without validation.
|
|
426
|
+
- Validate only the final assigned field when runtime type metadata is available; otherwise assign without validation.
|
|
428
427
|
|
|
429
428
|
Commit and mutation notes:
|
|
430
429
|
|
|
431
430
|
- Commit selection is controlled only by `<:name>` bindings.
|
|
432
431
|
- `<:name>` selects which top-level names are committed from `step_locals` into Python locals at Natural block boundaries.
|
|
433
|
-
- Dotted
|
|
432
|
+
- Dotted `nh_assign` on a write binding root marks that root as dirty for commit selection.
|
|
433
|
+
- Dotted `nh_assign` on a read binding root does not participate in commit selection.
|
|
434
|
+
- Final validation is applied only to committed write bindings at step finalization.
|
|
435
|
+
- Dotted mutation on read bindings and in-place mutation via `nh_eval` are outside the final validation guarantee.
|
|
434
436
|
|
|
435
437
|
Write tool return value:
|
|
436
438
|
|
|
@@ -449,6 +451,11 @@ All tool results are wrapped in a JSON envelope with the following structure:
|
|
|
449
451
|
|
|
450
452
|
Error kind categories: `invalid_input`, `resolution`, `execution`, `transient`, `internal`.
|
|
451
453
|
|
|
454
|
+
Boundary rule for tool-call failures:
|
|
455
|
+
|
|
456
|
+
- If the model can recover by changing tool arguments, choosing another tool, or continuing without the tool, the failure MUST be returned in the JSON envelope (`{"value": null, "error": ...}`).
|
|
457
|
+
- 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 envelope-wrapped.
|
|
458
|
+
|
|
452
459
|
The `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).
|
|
453
460
|
|
|
454
461
|
Atomicity requirement:
|
|
@@ -573,10 +580,11 @@ The required runtime object for step execution is:
|
|
|
573
580
|
|
|
574
581
|
- `step_executor` (required): a strategy object responsible for executing steps (Natural blocks).
|
|
575
582
|
|
|
576
|
-
Runtime execution identity is modeled separately in `
|
|
583
|
+
Runtime execution identity is modeled separately in `ExecutionRef`:
|
|
577
584
|
|
|
578
585
|
- `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.
|
|
579
586
|
- `scope_id`: the Id of the current (possibly nested) run scope. This serves as the identity of the current logical execution context.
|
|
587
|
+
- `step_id`: the Id of the current step when available. Outside active step execution it is `None`.
|
|
580
588
|
|
|
581
589
|
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.
|
|
582
590
|
|
|
@@ -585,21 +593,27 @@ API:
|
|
|
585
593
|
|
|
586
594
|
- `nighthawk.run(step_executor: StepExecutor, *, run_id: str | None = None)`
|
|
587
595
|
- Replaces the current context step executor with the provided step executor.
|
|
588
|
-
- Generates a new `
|
|
596
|
+
- Generates a new `ExecutionRef` for the duration of the `with`.
|
|
589
597
|
- Uses provided `run_id` when given; otherwise generates a new `run_id` (trace root).
|
|
590
598
|
- Always generates a fresh `scope_id`.
|
|
591
599
|
- Can be used even when no step executor is currently set.
|
|
592
|
-
- `nighthawk.scope(*, step_executor_configuration: StepExecutorConfiguration | None = None,
|
|
600
|
+
- `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]`
|
|
593
601
|
- Enter a nested scope within the current run.
|
|
594
602
|
- Requires an existing step executor.
|
|
595
603
|
- Generates a new `scope_id` (keeps the current `run_id`).
|
|
596
|
-
-
|
|
597
|
-
- `
|
|
598
|
-
|
|
604
|
+
- `oversight` omitted means inherit the current hooks; explicit `None` clears them for the nested scope.
|
|
605
|
+
- `mode="inherit"` (default):
|
|
606
|
+
- Appends prompt suffix fragment lists.
|
|
607
|
+
- Merges `implicit_references` additively with conflict checks.
|
|
608
|
+
- `mode="replace"`:
|
|
609
|
+
- Replaces provided list/mapping values.
|
|
610
|
+
- `None` means no change.
|
|
611
|
+
- Explicit `[]` / `{}` clears inherited list/mapping values.
|
|
612
|
+
- Explicit list/mapping values (for example `[e1, e2]` or `{k1: v1, k2: v2}`) fully replace inherited values.
|
|
599
613
|
- Yields the resolved `StepExecutor` for the scope.
|
|
600
614
|
- `nighthawk.get_step_executor() -> StepExecutor`
|
|
601
615
|
- Get the current step executor. Raises if unset.
|
|
602
|
-
- `nighthawk.
|
|
616
|
+
- `nighthawk.get_execution_ref() -> ExecutionRef`
|
|
603
617
|
- Get the current runtime execution identity. Raises if unset.
|
|
604
618
|
|
|
605
619
|
### 10.1. Observability contract (OpenTelemetry span/event)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from . import oversight, resilience
|
|
3
4
|
from .configuration import (
|
|
4
5
|
StepContextLimits,
|
|
5
6
|
StepExecutorConfiguration,
|
|
6
|
-
StepExecutorConfigurationPatch,
|
|
7
7
|
StepPromptTemplates,
|
|
8
8
|
)
|
|
9
9
|
from .errors import (
|
|
@@ -16,15 +16,15 @@ from .errors import (
|
|
|
16
16
|
)
|
|
17
17
|
from .json_renderer import JsonableValue, to_jsonable_value
|
|
18
18
|
from .natural.decorator import natural_function
|
|
19
|
-
from .runtime.scoping import
|
|
19
|
+
from .runtime.scoping import ExecutionRef, UsageMeter, get_current_usage_meter, get_execution_ref, get_step_executor, run, scope
|
|
20
20
|
from .runtime.step_context import StepContext, get_current_step_context
|
|
21
21
|
from .runtime.step_executor import AgentStepExecutor, StepExecutor
|
|
22
22
|
from .tools.registry import tool
|
|
23
23
|
|
|
24
24
|
__all__ = [
|
|
25
25
|
"AgentStepExecutor",
|
|
26
|
-
"ExecutionContext",
|
|
27
26
|
"ExecutionError",
|
|
27
|
+
"ExecutionRef",
|
|
28
28
|
"JsonableValue",
|
|
29
29
|
"NaturalParseError",
|
|
30
30
|
"NighthawkError",
|
|
@@ -32,7 +32,6 @@ __all__ = [
|
|
|
32
32
|
"StepContextLimits",
|
|
33
33
|
"StepExecutor",
|
|
34
34
|
"StepExecutorConfiguration",
|
|
35
|
-
"StepExecutorConfigurationPatch",
|
|
36
35
|
"StepPromptTemplates",
|
|
37
36
|
"ToolEvaluationError",
|
|
38
37
|
"ToolRegistrationError",
|
|
@@ -40,9 +39,11 @@ __all__ = [
|
|
|
40
39
|
"UsageMeter",
|
|
41
40
|
"get_current_step_context",
|
|
42
41
|
"get_current_usage_meter",
|
|
43
|
-
"
|
|
42
|
+
"get_execution_ref",
|
|
44
43
|
"get_step_executor",
|
|
44
|
+
"oversight",
|
|
45
45
|
"natural_function",
|
|
46
|
+
"resilience",
|
|
46
47
|
"run",
|
|
47
48
|
"scope",
|
|
48
49
|
"to_jsonable_value",
|
|
@@ -23,6 +23,7 @@ from pydantic_ai import RunContext
|
|
|
23
23
|
from pydantic_ai.exceptions import UnexpectedModelBehavior
|
|
24
24
|
from pydantic_ai.tools import ToolDefinition
|
|
25
25
|
|
|
26
|
+
from ..errors import NighthawkError
|
|
26
27
|
from ..runtime.step_context import (
|
|
27
28
|
DEFAULT_TOOL_RESULT_RENDERING_POLICY,
|
|
28
29
|
StepContext,
|
|
@@ -203,7 +204,7 @@ async def mcp_server_if_needed(
|
|
|
203
204
|
parent_otel_context = otel_context.get_current()
|
|
204
205
|
parent_run_context = get_current_run_context()
|
|
205
206
|
if parent_run_context is None:
|
|
206
|
-
raise
|
|
207
|
+
raise NighthawkError("Codex MCP tool server requires an active RunContext")
|
|
207
208
|
typed_parent_run_context = cast(RunContext[StepContext], parent_run_context)
|
|
208
209
|
tool_result_rendering_policy = typed_parent_run_context.deps.tool_result_rendering_policy
|
|
209
210
|
if tool_result_rendering_policy is None:
|