nighthawk-python 0.6.0__tar.gz → 0.6.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/CHANGELOG.md +13 -1
  2. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/PKG-INFO +1 -1
  3. nighthawk_python-0.6.1/docs/philosophy.ja.md +172 -0
  4. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/evals/promptfoo/provider.py +1 -0
  5. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/pyproject.toml +1 -1
  6. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/src/nighthawk/runtime/prompt.py +2 -1
  7. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/src/nighthawk/runtime/runner.py +73 -8
  8. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/src/nighthawk/runtime/step_context.py +1 -0
  9. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/src/nighthawk/testing.py +1 -1
  10. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/src/nighthawk/tools/provided.py +7 -7
  11. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/backends/test_codex.py +1 -0
  12. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/docs/test_prompt_examples.py +1 -0
  13. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/execution/prompt_test_helpers.py +15 -1
  14. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/execution/test_variables_prompt.py +213 -0
  15. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/tools/test_assignment_async.py +4 -0
  16. nighthawk_python-0.6.1/tests/tools/test_provided_async.py +106 -0
  17. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/tools/test_tool_boundary.py +1 -0
  18. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/uv.lock +1 -1
  19. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/.claude/rules/coding.md +0 -0
  20. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/.claude/rules/docs.md +0 -0
  21. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/.claude/rules/promptfoo.md +0 -0
  22. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/.claude/rules/tests.md +0 -0
  23. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/.claude/settings.json +0 -0
  24. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/.claude/unset_envs.sh +0 -0
  25. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/.devcontainer/Dockerfile.devcontainer +0 -0
  26. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/.devcontainer/Dockerfile.litellm +0 -0
  27. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/.devcontainer/devcontainer.json +0 -0
  28. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/.devcontainer/docker-compose.yaml +0 -0
  29. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/.devcontainer/litellm-config.yaml +0 -0
  30. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/.github/dependabot.yml +0 -0
  31. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/.github/workflows/ci.yml +0 -0
  32. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/.github/workflows/docs.yml +0 -0
  33. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/.github/workflows/publish.yml +0 -0
  34. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/.gitignore +0 -0
  35. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/.python-version +0 -0
  36. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/AGENTS.md +0 -0
  37. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/CLAUDE.md +0 -0
  38. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/CONTRIBUTING.md +0 -0
  39. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/LICENSE +0 -0
  40. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/README.md +0 -0
  41. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/docs/AGENTS.md +0 -0
  42. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/docs/api.md +0 -0
  43. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/docs/assets/nighthawk_logo-128x128.png +0 -0
  44. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/docs/coding-agent-backends.md +0 -0
  45. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/docs/executors.md +0 -0
  46. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/docs/for-coding-agents.md +0 -0
  47. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/docs/index.md +0 -0
  48. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/docs/natural-blocks.md +0 -0
  49. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/docs/patterns.md +0 -0
  50. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/docs/philosophy.md +0 -0
  51. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/docs/pydantic-ai-providers.md +0 -0
  52. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/docs/quickstart.md +0 -0
  53. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/docs/roadmap.md +0 -0
  54. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/docs/runtime-configuration.md +0 -0
  55. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/docs/specification.md +0 -0
  56. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/docs/verification.md +0 -0
  57. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/evals/promptfoo/assertions/__init__.py +0 -0
  58. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/evals/promptfoo/assertions/binding_value.py +0 -0
  59. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/evals/promptfoo/assertions/outcome_kind.py +0 -0
  60. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/evals/promptfoo/assertions/raise_message.py +0 -0
  61. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/evals/promptfoo/evidence/2026-03-26-baseline-prompt-ab.md +0 -0
  62. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/evals/promptfoo/evidence/2026-03-26-baseline-regression.md +0 -0
  63. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/evals/promptfoo/evidence/2026-03-26-baseline-suffix-ab.md +0 -0
  64. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/evals/promptfoo/promptfooconfig-agents.yaml +0 -0
  65. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/evals/promptfoo/promptfooconfig-prompt-ab.yaml +0 -0
  66. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/evals/promptfoo/promptfooconfig-suffix-ab.yaml +0 -0
  67. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/evals/promptfoo/promptfooconfig.yaml +0 -0
  68. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/evals/promptfoo/prompts/eval_coding_agent.txt +0 -0
  69. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/evals/promptfoo/prompts/eval_default.txt +0 -0
  70. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/evals/promptfoo/prompts/eval_mutation_aware.txt +0 -0
  71. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/evals/promptfoo/prompts/eval_sequenced.txt +0 -0
  72. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/evals/promptfoo/research-result.md +0 -0
  73. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/evals/promptfoo/test_cases/binding_operations.yaml +0 -0
  74. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/evals/promptfoo/test_cases/edge_cases.yaml +0 -0
  75. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/evals/promptfoo/test_cases/loop_outcomes.yaml +0 -0
  76. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/evals/promptfoo/test_cases/multi_step.yaml +0 -0
  77. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/evals/promptfoo/test_cases/null_handling.yaml +0 -0
  78. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/evals/promptfoo/test_cases/outcome_kinds.yaml +0 -0
  79. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/evals/promptfoo/test_cases/tool_selection.yaml +0 -0
  80. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/mkdocs.yml +0 -0
  81. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/pyrightconfig.json +0 -0
  82. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/src/nighthawk/__init__.py +0 -0
  83. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/src/nighthawk/backends/__init__.py +0 -0
  84. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/src/nighthawk/backends/base.py +0 -0
  85. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/src/nighthawk/backends/claude_code_cli.py +0 -0
  86. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/src/nighthawk/backends/claude_code_sdk.py +0 -0
  87. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/src/nighthawk/backends/claude_code_settings.py +0 -0
  88. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/src/nighthawk/backends/codex.py +0 -0
  89. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/src/nighthawk/backends/mcp_boundary.py +0 -0
  90. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/src/nighthawk/backends/mcp_server.py +0 -0
  91. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/src/nighthawk/backends/tool_bridge.py +0 -0
  92. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/src/nighthawk/configuration.py +0 -0
  93. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/src/nighthawk/errors.py +0 -0
  94. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/src/nighthawk/identifier_path.py +0 -0
  95. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/src/nighthawk/json_renderer.py +0 -0
  96. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/src/nighthawk/natural/__init__.py +0 -0
  97. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/src/nighthawk/natural/blocks.py +0 -0
  98. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/src/nighthawk/natural/decorator.py +0 -0
  99. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/src/nighthawk/natural/transform.py +0 -0
  100. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/src/nighthawk/resilience/__init__.py +0 -0
  101. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/src/nighthawk/resilience/_circuit_breaker.py +0 -0
  102. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/src/nighthawk/resilience/_fallback.py +0 -0
  103. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/src/nighthawk/resilience/_retry.py +0 -0
  104. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/src/nighthawk/resilience/_timeout.py +0 -0
  105. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/src/nighthawk/resilience/_vote.py +0 -0
  106. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/src/nighthawk/runtime/__init__.py +0 -0
  107. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/src/nighthawk/runtime/async_bridge.py +0 -0
  108. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/src/nighthawk/runtime/scoping.py +0 -0
  109. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/src/nighthawk/runtime/step_contract.py +0 -0
  110. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/src/nighthawk/runtime/step_executor.py +0 -0
  111. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/src/nighthawk/runtime/tool_calls.py +0 -0
  112. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/src/nighthawk/tools/__init__.py +0 -0
  113. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/src/nighthawk/tools/assignment.py +0 -0
  114. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/src/nighthawk/tools/contracts.py +0 -0
  115. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/src/nighthawk/tools/execution.py +0 -0
  116. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/src/nighthawk/tools/registry.py +0 -0
  117. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/src/nighthawk/ulid.py +0 -0
  118. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/__init__.py +0 -0
  119. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/backends/__init__.py +0 -0
  120. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/backends/test_claude_code_cli.py +0 -0
  121. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/backends/test_claude_code_sdk.py +0 -0
  122. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/conftest.py +0 -0
  123. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/docs/__init__.py +0 -0
  124. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/docs/test_coding_agent_examples.py +0 -0
  125. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/docs/test_docs_architecture.py +0 -0
  126. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/execution/__init__.py +0 -0
  127. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/execution/stub_executor.py +0 -0
  128. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/execution/test_execution_outcome_prompt_fragment.py +0 -0
  129. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/execution/test_globals_prompt.py +0 -0
  130. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/execution/test_infer_binding_types.py +0 -0
  131. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/execution/test_natural_block_ordering.py +0 -0
  132. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/execution/test_natural_traceback.py +0 -0
  133. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/execution/test_runtime.py +0 -0
  134. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/integration/__init__.py +0 -0
  135. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/integration/skip_helpers.py +0 -0
  136. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/integration/test_carry_pattern.py +0 -0
  137. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/integration/test_claude_code_cli_integration.py +0 -0
  138. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/integration/test_claude_code_sdk_integration.py +0 -0
  139. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/integration/test_codex_integration.py +0 -0
  140. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/integration/test_llm_integration.py +0 -0
  141. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/natural/__init__.py +0 -0
  142. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/natural/test_blocks.py +0 -0
  143. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/public/__init__.py +0 -0
  144. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/public/test_public_api.py +0 -0
  145. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/public/test_readme_example.py +0 -0
  146. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/resilience/__init__.py +0 -0
  147. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/resilience/test_circuit_breaker.py +0 -0
  148. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/resilience/test_composition.py +0 -0
  149. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/resilience/test_fallback.py +0 -0
  150. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/resilience/test_retry.py +0 -0
  151. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/resilience/test_timeout.py +0 -0
  152. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/resilience/test_vote.py +0 -0
  153. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/test_renderer.py +0 -0
  154. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/test_testing.py +0 -0
  155. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/tools/__init__.py +0 -0
  156. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/tools/test_contracts.py +0 -0
  157. {nighthawk_python-0.6.0 → nighthawk_python-0.6.1}/tests/tools/test_registry.py +0 -0
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.6.1]
11
+
12
+ ### Added
13
+ - Implicit type alias discovery: callable signatures in step locals and referenced globals are now scanned for PEP 695 `TypeAliasType` references, automatically including their definitions in the prompt globals section so the LLM can resolve type names like `-> Labels`.
14
+
15
+ ### Changed
16
+ - `nh_eval` and `nh_assign` provided tools are now async, directly awaiting coroutines in async contexts instead of bridging through a background thread.
17
+
18
+ ## [0.6.0]
19
+
10
20
  ### Added
11
21
  - `nighthawk.resilience` module with composable function transformers for production resilience: `retrying` (tenacity-based), `fallback`, `vote`/`plurality`, `timeout`, `circuit_breaker`/`CircuitState`/`CircuitOpenError`.
12
22
  - `tenacity>=9` as a core dependency.
@@ -92,7 +102,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
92
102
  - Step executor abstraction and provider integration foundation.
93
103
  - Core documentation and project scaffolding.
94
104
 
95
- [Unreleased]: https://github.com/kurusugawa-computer/nighthawk-python/compare/v0.5.0...HEAD
105
+ [Unreleased]: https://github.com/kurusugawa-computer/nighthawk-python/compare/v0.6.1...HEAD
106
+ [0.6.1]: https://github.com/kurusugawa-computer/nighthawk-python/compare/v0.6.0...v0.6.1
107
+ [0.6.0]: https://github.com/kurusugawa-computer/nighthawk-python/compare/v0.5.0...v0.6.0
96
108
  [0.5.0]: https://github.com/kurusugawa-computer/nighthawk-python/compare/v0.4.0...v0.5.0
97
109
  [0.4.0]: https://github.com/kurusugawa-computer/nighthawk-python/compare/v0.3.1...v0.4.0
98
110
  [0.3.1]: https://github.com/kurusugawa-computer/nighthawk-python/compare/v0.3.0...v0.3.1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nighthawk-python
3
- Version: 0.6.0
3
+ Version: 0.6.1
4
4
  Summary: An experimental Python library that embeds Natural blocks inside Python functions and executes them using an LLM.
5
5
  Project-URL: Repository, https://github.com/kurusugawa-computer/nighthawk-python
6
6
  Project-URL: Documentation, https://kurusugawa-computer.github.io/nighthawk-python/
@@ -0,0 +1,172 @@
1
+ # 設計思想
2
+
3
+ Python がオーケストレーションを制御し、LLM は明示的な状態転送を伴う型付きブロックの内部で動作する。
4
+
5
+ ## 実行モデル
6
+
7
+ Nighthawk は通常の Python 関数の内部に Natural ブロックを埋め込む。各ブロックは型付き境界である。読み取りバインディング (`<name>`) は Python 変数から入力状態を注入し、書き込みバインディング (`<:name>`) は型検証を経て出力状態をコミットし、バインディング関数はブロック実行中に LLM へ Python の呼び出し可能オブジェクトへのコンポーザブルなアクセスを提供する。Python がシーケンス制御 -- ループ、条件分岐、エラーハンドリング、リトライ -- を担い、LLM は各ブロック内部で動作する。ブロック間で暗黙的なメッセージ履歴が引き継がれることはない。
8
+
9
+ ```py
10
+ def python_average(numbers):
11
+ return sum(numbers) / len(numbers)
12
+
13
+ @nh.natural_function
14
+ def calculate_average(numbers):
15
+ """natural
16
+ Map each element of <numbers> to the number it represents,
17
+ then compute <:result> by calling <python_average> with the mapped list.
18
+ """
19
+ return result
20
+
21
+ calculate_average([1, "2", "three", "cuatro", "五"]) # 3.0
22
+ ```
23
+
24
+ `<python_average>` のようなバインディング関数はプロンプト内でコンパクトなシグネチャ行として表示される。LLM の事前学習済み Python 知識により、JSON Schema やプロトコルのオーバーヘッドなしに、シグネチャだけで型、戻り値、合成について推論できる。定量的な比較については [ツール公開効率](#ツール公開効率) を参照。
25
+
26
+ プロバイダーバックドエグゼキューターでは、各 Natural ブロックは型付きバインディングが主要な処理を担う単一の LLM 呼び出しである。書き込みバインディングが `Literal["positive", "negative", "neutral"]` として型付けされた感情分類器は、宣言された集合外の出力を拒否する -- 型アノテーションはヒントではなく、Pydantic 検証による実行時強制契約である。同じメカニズムは数値抽出 (`int`, `float`)、構造化パース (Pydantic モデル)、判断空間が有界な任意のタスクに適用される。ホストプログラムがループを所有するため、誤分類された結果はリトライ、ログ記録、フォールバックへの転送が可能であり -- すべて通常の Python で行える。
27
+
28
+ [コーディングエージェントバックエンド](coding-agent-backends.md) では、同じ境界契約が適用されるが、各 Natural ブロックは自律的なエージェント実行となる。エージェントはファイルの読み取り、コマンドの実行、スキルの呼び出しが可能であり -- 型付きバインディングが Python への境界を越える際に何を通過させるかを強制する。人間が記述するワークフローを構造化する `scope()` や `run()` のコンテキストマネージャーは、コーディングエージェントがプログラム的にワークフローを構築する際にも同様に読解可能である。コーディングエージェントが Natural ブロック内で動作する場合、バインディング関数はプロンプト内で Python シグネチャとして表示される:
29
+
30
+ ```
31
+ fetch_items: (category: str, limit: int = 10) -> list[Item]
32
+ merge_results: (primary: list[Item], secondary: list[Item]) -> list[Item]
33
+ ```
34
+
35
+ 基盤となる LLM の事前学習済み Python 知識により、`Item` が属性を持つこと、戻り値がイテレーションやインデキシングをサポートすること、`merge_results` が `fetch_items` の出力を直接受け取れることを -- 型アノテーションだけから推論できる。同等の CLI ツール記述 (`fetch-items --category X --limit 10`) は呼び出し構文を伝えるが、出力構造は伝えない。モデルは出力形式を別途推論または発見する必要がある。
36
+
37
+ コーディングエージェントバックエンドがこれを特に実用的にするのは、エージェントが推論された構造をワークフローコードの読解、ツールの呼び出し、実装の編集、`pytest` の実行、同一 Python コードベース内でのイテレーションに即座に適用できるためである。フレームワーク固有のツール、グラフシリアライゼーション形式、別の設定言語は不要である。
38
+
39
+ ## ハーネスはモデルより重要
40
+
41
+ 最も強い直接的証拠はエージェント型コーディングタスクから得られている。この原則をプロバイダーバックドの判断に拡張することは設計上の推論であり、測定された主張ではない。
42
+
43
+ ### 観察された証拠
44
+
45
+ 経験的証拠は、周囲のプログラムがモデルより重要であることを示唆している。Can Boluk の [2026年の実験](https://blog.can.ac/2026/02/12/the-harness-problem/) は、16 モデル x 3 編集ツール x 180 タスクでテストし、ハーネスのみの変更であるモデルの成功率が 6.7% から 68.3% に向上した -- モデル変更なしで10倍の改善である。LangChain も同様のパターンを報告しており (2026年)、ハーネス変更のみでコーディングエージェントの精度を 52.8% から 66.5% に改善した。
46
+
47
+ Mitchell Hashimoto は 2026年2月に [この実践を「ハーネスエンジニアリング」と命名した](https://mitchellh.com/writing/my-ai-adoption-journey): 「エージェントがミスを犯すたびに、そのエージェントが二度とそのミスを犯さないようにソリューションをエンジニアリングする時間を取る。」 OpenAI は同月、[ハーネスファースト開発の詳細な記述](https://openai.com/index/harness-engineering/) を公開した。
48
+
49
+ 直接的な証拠は LLM 駆動のコード編集およびファイル管理タスクに関するものであり、ハーネス設計 (編集フォーマット、ツール構成、コンテキスト管理) がモデル選択よりも大きな改善をもたらした。これらのタスクはマルチステップのツール使用とファイル操作を含み、シングルターンの分類や抽出とは構造的に異なる。
50
+
51
+ ### Nighthawk の設計上の推論
52
+
53
+ この原則をプロバイダーバックドの軽量判断 (感情分類、数値解釈) に拡張することは、経験的主張ではなく設計上の推論である。型付きバインディングは構造的にハルシネーションを制約し、レジリエンストランスフォーマーは一時的な障害を吸収するが、これらの利点は同じ統制された方法で独立に測定されていない。
54
+
55
+ スコープにかかわらず、実践的な問いはハーネス改善がどのように表現されるかである。設定ファイルベースのガードレールシステム -- ルールファイル、ライフサイクルフック、パーミッションモード、ツールフィルタリング -- は動作の制限には効果的だが、動的なオーケストレーションを表現できない。すなわち、条件付きリトライ戦略、型レベルの入出力契約、スコープ依存のツール可視性、実行時状態に適応するプロンプトは表現できない。制約の語彙は設定フォーマットが許容する範囲に限定される。
56
+
57
+ [実行モデル](#実行モデル) および以降のセクションで述べるプリミティブ -- 型付きバインディング、レジリエンストランスフォーマー、スコープ付き実行コンテキスト -- は、設定ではなく Python プログラミングを通じたこの原則の Nighthawk による実装である。
58
+
59
+ ## 設計上の帰結
60
+
61
+ 実行モデルでは、Python と LLM の推論の間の境界メカニズムとして型付きバインディングを導入した。以下のサブセクションでは、その選択からどのような設計上の帰結が生じるかを探る -- レジリエンスとスコーピングからツール公開、マルチエージェント協調、そして設計が受け入れるトレードオフまで。
62
+
63
+ ### コンポーザブル関数としてのレジリエンス
64
+
65
+ プロダクション LLM アプリケーションには、一時的な障害、不安定な出力、プロバイダー障害に対処する戦略が必要である。ワークフローエンジンはリトライ、チェックポイント、Human-in-the-Loop をグラフランタイムに組み込む -- レジリエンスはオーケストレーション層と不可分である。Nighthawk は異なるアプローチを取る: レジリエンスプリミティブ (`nighthawk.resilience`) は任意の呼び出し可能オブジェクトをラップする通常の Python 関数トランスフォーマーである。各トランスフォーマーは関数を受け取り、同じシグネチャの新しい関数を返す。リトライ、フォールバック、投票、タイムアウト、サーキットブレーカーのロジックはネストによって合成される -- グラフ DSL なし、フレームワーク管理の状態なし、暗黙的なリトライポリシーなし。ホストがどの呼び出しをリトライするか、何回リトライするか、失敗時に何が起こるかを正確に制御する -- アプリケーションの他の部分と同じ Python デバッガー、pytest、コードレビューのワークフローを使用して。これはプロバイダーバックドの軽量判断と自律的なエージェント実行の両方に等しく適用される。使用パターンと合成の例については [パターン](patterns.md#resilience-patterns) を参照。
66
+
67
+ ### スコープ付き実行コンテキスト
68
+
69
+ `run()` は実行境界を確立する: グローバル設定や暗黙的なスレッドローカルとしてではなく、明示的な Python `with` 文として現在のコンテキストにステップエグゼキューターをリンクする。`scope()` は既存の run 内で設定を狭める -- モデルオーバーライド、プロンプトサフィックス、エグゼキューター置換 -- それぞれネストされた `with` ブロック内でのみ有効となる。ネストは自然な Python のレキシカルスコーピングである: フレームワークランタイムではなくホストプログラムの制御フローが、任意の時点でどの設定がアクティブかを決定する。これは、実行時の動作がプロのみの指示や静的設定ではなく Python 構造に存在すべきという思想に直接つながる。詳細と例については [ランタイム設定](runtime-configuration.md) を参照。
70
+
71
+ ### ツール公開効率
72
+
73
+ バインディング関数は JSON Schema オブジェクトや CLI 記述ではなく Python シグネチャであるため、ツールあたりのコンテキストコストは単一のシグネチャ行程度である。MCP ツール定義はリクエストごとの JSON Schema オーバーヘッドを伴い、公開ツール数に応じて増大する。CLI ツールは定義オーバーヘッドを削減するが、隠れたコストを伴う -- Mario Zechner の [2025年のベンチマーク](https://mariozechner.at/posts/2025-08-15-mcp-vs-cli/) によれば、Claude Code での CLI 呼び出しはコマンドごとのセキュリティ分類をトリガーし、同等の MCP 呼び出しよりも桁違いに多くのトークンを消費した。いずれのアプローチでも、モデルが実際のタスクを見る前にツールの配管に相当なコンテキスト予算が費やされる。
74
+
75
+ **MCP** はツールをプロトコル層上の JSON Schema オブジェクトとして定義する。各ツール定義はリクエストごとにトークンを消費する。
76
+
77
+ **CLI ツール** は LLM のシェルコマンドに関する事前学習済み知識を活用することで大幅に改善される。同等の CLI ツールの README は、わずか 225 トークンで同じ機能を記述できる。しかし、CLI は型なし文字列 I/O で動作する: 構造化データはテキストにシリアライズしてパースし直す必要があり、型安全性は強制ではなく慣例に依存し、テストにはシェルレベルのスキャフォールディングが必要である。CLI の出力構造は宣言されないため、LLM は学習データから推論する必要がある -- マルチステップのツール合成が構造的保証ではなく確率的想起に依存することになる。
78
+
79
+ **Nighthawk のバインディング関数** は CLI の洞察をさらに一歩進める。LLM は bash と同様に Python も熟知している。バインディング関数はプロンプト内で単一のシグネチャ行として表示される:
80
+
81
+ ```
82
+ find_top_items: (category: str) -> list[dict] # Return the highest-scored recent items in a category.
83
+ ```
84
+
85
+ これは単一のシグネチャ行程度であり -- 最もコンパクトな CLI 記述と同等のトークンコストだが、より高い情報密度を持つ。型アノテーションにより LLM は構造的に推論できる: `list[dict]` の戻り値はイテレーションとキーアクセスをサポートし、`Item` の戻り型は発見可能な属性を持ち、型付きパラメータは別のバインディング関数が何を受け入れるかを明確にする。同様のコンパクトさの CLI 記述は呼び出し構文を伝えるが、出力構造は学習データからの推論に委ねる。プロトコル層なし、シリアライゼーション境界なし、ツールごとの JSON Schema オーバーヘッドなし。同じ型アノテーションがオプションの静的解析 (pyright) のターゲットおよび Nighthawk のランタイム検証 (Pydantic 経由) のフックとして機能する。テスト、デバッグ、合成には標準的な Python ツールを使用する。
86
+
87
+ | アプローチ | ツールあたりのコンテキストコスト | 情報密度 | 型安全性 | コンポーザビリティ | テスト | 相互運用性 |
88
+ |---|---|---|---|---|---|---|
89
+ | MCP | 高い (ツールごとの JSON Schema) | 低い (冗長なスキーマ) | スキーマレベル | フレームワーク依存 | フレームワーク固有 | 言語横断標準 |
90
+ | CLI | 低い (事前学習済み知識) | 中程度 (出力は推論) | なし (文字列 I/O) | パイプ (線形、文字列ベース) | シェルスクリプト | 汎用 (任意のランタイム) |
91
+ | バインディング関数 | 低い (シグネチャ1行) | 高い (型 + セマンティクス) | アノテーションベース (静的解析 + 書き込み境界のランタイム強制) | ネイティブ (関数合成) | pytest | Python のみ |
92
+
93
+ ### フレームワークなしのマルチエージェント協調
94
+
95
+ マルチエージェントシステムは3つの構造的課題に直面する: エージェント間の状態伝達方法、エージェント間の隔離方法、複数エージェントの結果の統合方法。既存のワークフローエンジンはフレームワーク固有のメカニズムでこれらに対処する -- 通信にはグラフ状態、隔離にはマネージドランタイム、統合にはメッセージ集約 -- しかし、各ソリューションはユーザーをフレームワークの抽象化にロックインし、3つすべてを包括的に提供するフレームワークは存在しない。
96
+
97
+ Nighthawk はマルチエージェントフレームワークではない。各課題に対して Python の既存エコシステムと合成するビルディングブロックである。
98
+
99
+ **通信。** 関数内の Natural ブロック間では Python 変数が状態を引き継ぐ -- 読み取りバインディング (`<name>`) が値を公開し、書き込みバインディング (`<:name>`) が型検証付きで新しい値をコミットする。Natural 関数間の通信は通常の Python である: 戻り値、関数引数、共有データ構造。メッセージブローカーなし、グラフ状態なし、フレームワーク管理のチャネルなし。クロスプロセスまたは分散協調には、任意の Python ネイティブメカニズム (asyncio、キュー、タスクブローカー) が Natural 関数呼び出しをオーケストレートできる。なぜなら、それらは通常の Python の呼び出し可能オブジェクトだからである。
100
+
101
+ **隔離。** Nighthawk はバインディング境界での論理的隔離を提供する: 読み取りバインディングは名前の再バインドを防ぎ、書き込みバインディングは型検証され、各 Natural ブロックは暗黙的なメッセージ履歴を持たない独立したステップコンテキストで実行される。読み取りバインディングはミュータブルオブジェクトのインプレース変更を防がない -- これは意図的であり、[キャリーパターン](patterns.md#the-carry-pattern) の基盤となっている。OS レベルの隔離 -- サンドボックス、ファイルシステムスコーピング、パーミッション制御 -- は実行バックエンドに委譲される。コーディングエージェントバックエンドは独自のサンドボックスモードと作業ディレクトリスコーピングを提供し、Nighthawk はそれらを設定するが再実装はしない。
102
+
103
+ **結果統合。** レジリエンスモジュールは一般的なケースのためのコンポーザブルなパターンを提供する: 繰り返し呼び出しにおける多数決合意のための `vote`、順次の最初の成功連鎖のための `fallback`。ドメイン固有の統合 -- 複数エージェントからの編集の調整、異種出力の集約、競合の解決 -- はユーザーコードに属する。なぜなら、統合セマンティクスは本質的にドメイン依存だからである。Nighthawk の役割は、各エージェントの出力が型付けされ検証された Python オブジェクトとして境界を越え、統合ロジックが直接操作できることを保証することである。
104
+
105
+ ### トレードオフ
106
+
107
+ 境界中心の設計にはコストがある:
108
+
109
+ - **Python ロックイン。** バインディング関数、型アノテーション、レジリエンストランスフォーマーは Python の構成要素である。Nighthawk は言語中立のプロトコルを提供しない。非 Python システムとの相互運用には明示的なブリッジング (例: Natural 関数をラップする REST エンドポイント) が必要である。
110
+ - **呼び出しごとのコスト。** Natural ブロックの呼び出しごとに LLM を呼び出す。入力間でコストを償却するコンパイルステップは存在しない。決定論的な Python 関数で十分な高スループット・低判断タスクには、Natural ブロックは適切なツールではない。設計の根拠については [なぜ毎回評価するのか](#なぜ毎回評価するのか) を参照。
111
+ - **インテグレーションテストは必須。** モックテストは Natural ブロック周辺の Python ロジックを検証するが、LLM が正しい判断を生成することの検証には実プロバイダーに対するインテグレーションテストが必要である。[二層テスト戦略](verification.md) はオプションではなく、判断を LLM に委譲することの構造的帰結である。
112
+ - **手動オーケストレーション負担。** Nighthawk は分岐、リトライ、統合ロジック、リカバリーポリシーをグラフランタイムではなくユーザーコードに残す。これは「Python がすべてのフローを制御する」原則の直接的コストである。
113
+ - **Python API 設計の規律。** バインディング関数はそのシグネチャ、型アノテーション、命名の質に依存する。API 設計が不適切だと、合成についての LLM の推論能力が低下する。
114
+
115
+ ## なぜ毎回評価するのか
116
+
117
+ 自然な疑問: Natural ブロックを一度使って同等の Python コードに変換し、以降の呼び出しで生成されたコードを実行すればよいのではないか? これにより呼び出しごとのレイテンシ、コスト、非決定性が排除される。
118
+
119
+ 答えは、Natural ブロックは決定論的コードに還元できないタスクのために存在するということである。「このレビューの感情を分類せよ」や「この曖昧なユーザー入力を解釈せよ」は、特定の入力、世界知識、コンテキストに依存する判断を必要とする。タスクが決定論的な Python として記述できるなら、そうすべきである -- これが核心的な設計原則である ([Natural ブロック](natural-blocks.md#responsibility-split) を参照)。
120
+
121
+ 一度きりのコンパイルにはさらなる構造的限界がある:
122
+
123
+ - 生成されたコードはコンパイル時点の LLM の世界知識を固定してしまう。
124
+ - 入力空間は無限である: 「りんご3個、卵1ダース、そして cinco naranjas」は、有限のコード生成では完全に予測できないオープンエンドの解釈を必要とする。
125
+ - 生成されたコードの正しさの検証には最終的に LLM が必要であり -- 循環依存を生む。
126
+
127
+ [コーディングエージェントバックエンド](coding-agent-backends.md) では、「毎回評価する」は各 Natural ブロック呼び出しごとに自律的エージェントを起動し、特定の入力に完全に適応することを意味する。呼び出しごとのコストは高いが、適応性も高い。
128
+
129
+ Nighthawk はコンパイルではなく制約によって信頼性の懸念に対処する: 書き込みバインディングの型検証、許可する結果を制限する deny フロントマター、制御フローのための構造化アウトカムカインド、そして [二層テスト戦略](verification.md) (Python ロジックのモックテスト、Natural ブロックの有効性のインテグレーションテスト)。
130
+
131
+ ## 設計のランドスケープ
132
+
133
+ 現在の設計空間における3つのポジションが、オーケストレーションアプローチの幅を示している: オーケストレーションフレームワーク、文芸的プログラミングスタイルのハーネス、そして Nighthawk。
134
+
135
+ ### オーケストレーションフレームワーク
136
+
137
+ **LangGraph、CrewAI、AutoGen。** LLM またはグラフランタイムが次に何が起こるかを決定する -- どのツールを呼び出すか、エージェント間のルーティング方法、いつ停止するか。契約はフレームワーク管理のスキーマ、ガードレール、ルーティング条件を通じて強制される。状態はメッセージとしてグラフを流れ、会話履歴はステップ間で暗黙的に蓄積される。
138
+
139
+ ### 文芸的プログラミングスタイルのハーネス
140
+
141
+ **Agent Skills および類似のアプローチ。** オーケストレーションロジックはホストプログラムの型システムの外部に存在する -- 厳密な手続きのための埋め込みコードを伴う自然言語の指示の中に。制約は自然言語で表現され、確率的に強制される。オーケストレーションと状態が自然言語に存在する場合、プロンプト世界とコード世界の間で実行状態を同期させることは困難である。以下の例は状態同期の課題を示している:
142
+
143
+ ````md
144
+ Compute the average using `calculate_average`.
145
+ Convert the mixed representations before calling it.
146
+
147
+ ```py
148
+ def calculate_average(numbers):
149
+ return sum(numbers) / len(numbers)
150
+ ```
151
+
152
+ Target: `[1, "2", "three", "cuatro", "五"]`
153
+ Store the computed average in `result`.
154
+ ````
155
+
156
+ この指示は埋め込みコードを参照するが、`result` がどのようにホストプログラムに戻るかの明示的な境界は存在しない。ナラティブは値が後続のステップで利用可能であることを前提としているが、状態転送のメカニズムは暗黙的であり -- 読者は宣言された契約ではなく慣例から推論しなければならない。
157
+
158
+ ### Nighthawk
159
+
160
+ ホスト Python プログラムがオーケストレーションを所有する。契約は Python の型で表現され、ランタイム検証、構造化アウトカム、明示的なブロック境界を通じて強制される。状態はブロック境界での明示的な転送を伴う Python 変数に存在する。全体の説明については [実行モデル](#実行モデル) を参照。
161
+
162
+ | | オーケストレーションフレームワーク | 文芸的ハーネス | Nighthawk |
163
+ |---|---|---|---|
164
+ | 制御 | LLM がグラフ/ルーティングでオーケストレート | 自然言語の指示 | Python がすべてのフローを制御 |
165
+ | 状態 | グラフ状態、メッセージ履歴 | プロンプトナラティブに埋め込み | Python ローカル変数、明示的バインディング |
166
+ | ステップ間コンテキスト | 暗黙的 (会話が蓄積) | 暗黙的 (プロンプト継続) | 明示的 (バインディング、スコープ付きインジェクション) |
167
+ | デバッグ | フレームワーク固有のツール | プロンプト検査 | Python デバッガー、pytest |
168
+ | 制約モデル | ガードレール、ルーティング条件 | 自然言語 (確率的) | 型検証、deny フロントマター、構造化アウトカム |
169
+
170
+ 静的な制約システム -- AGENTS.md スタイルのルールファイル、ライフサイクルフック、パーミッションモード -- は上記のいずれのアプローチの周囲にもガードレール層として有用であるが、ランタイムオーケストレーションや型付き状態転送を代替するものではない。
171
+
172
+ オーケストレーションフレームワークは、マルチエージェント協調がタスクの核心である場合、または蓄積された会話履歴が不可欠な場合 (例: チャットボット) により適している。文芸的プログラミングスタイルのハーネスは、オーケストレーションロジックが散文で最も自然に表現されるシナリオや、対象読者がコードではなく指示を記述する場合に適している。Nighthawk は、決定論的制御フローが離散的な判断ポイントを含む場合、LLM の推論を既存の Python コードベースに統合する場合、または各判断に厳密な入出力制約が必要な場合により適している。
@@ -413,6 +413,7 @@ def call_api(prompt: str, options: dict, context: dict) -> dict: # noqa: ARG001
413
413
  step_locals=step_locals,
414
414
  binding_commit_targets=set(output_binding_names),
415
415
  read_binding_names=read_binding_names,
416
+ implicit_type_reference_names=frozenset(),
416
417
  )
417
418
 
418
419
  # -- Execute --
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nighthawk-python"
3
- version = "0.6.0"
3
+ version = "0.6.1"
4
4
  description = "An experimental Python library that embeds Natural blocks inside Python functions and executes them using an LLM."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
@@ -318,9 +318,10 @@ def build_user_prompt(
318
318
  )
319
319
 
320
320
  references, program_text = extract_references_and_program(processed_natural_program)
321
+ augmented_global_references = set(references) | set(step_context.implicit_type_reference_names)
321
322
  globals_text = _render_globals_section(
322
323
  step_context=step_context,
323
- references=references,
324
+ references=augmented_global_references,
324
325
  token_encoding=token_encoding,
325
326
  context_limits=context_limits,
326
327
  json_renderer_style=configuration.json_renderer_style,
@@ -1,10 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import ast
4
+ import functools
4
5
  import inspect
6
+ import typing
7
+ from collections.abc import Iterable
5
8
  from dataclasses import dataclass
6
9
  from types import FrameType
7
- from typing import TypedDict
10
+ from typing import TypeAliasType, TypedDict
8
11
 
9
12
  from opentelemetry.trace import Span, Status, StatusCode
10
13
  from pydantic import TypeAdapter
@@ -56,13 +59,6 @@ def _infer_binding_types_from_initial_values(
56
59
  binding_name_to_type: dict[str, object],
57
60
  step_locals: dict[str, object],
58
61
  ) -> None:
59
- """Replace ``object`` fallback types with types inferred from initial values.
60
-
61
- When a binding has no explicit type annotation, the AST transformer assigns
62
- ``object`` as a placeholder. This function upgrades those entries to the
63
- runtime type of the initial value so that ``TypeAdapter`` validation in
64
- ``nh_assign`` can catch type mismatches and prompt the LLM to retry.
65
- """
66
62
  for name, declared_type in binding_name_to_type.items():
67
63
  if declared_type is not object:
68
64
  continue
@@ -74,6 +70,69 @@ def _infer_binding_types_from_initial_values(
74
70
  binding_name_to_type[name] = inferred_type
75
71
 
76
72
 
73
+ def _discover_implicit_type_alias_reference_names(
74
+ *,
75
+ step_locals: dict[str, object],
76
+ step_globals: dict[str, object],
77
+ input_binding_names: Iterable[str],
78
+ ) -> frozenset[str]:
79
+ discovered_names: set[str] = set()
80
+ seen: set[int] = set()
81
+
82
+ def _collect(annotation: object) -> None:
83
+ if isinstance(annotation, TypeAliasType):
84
+ name = annotation.__name__
85
+ if name in step_globals and name not in step_locals:
86
+ discovered_names.add(name)
87
+ return
88
+
89
+ if isinstance(annotation, str):
90
+ resolved = step_globals.get(annotation)
91
+ if isinstance(resolved, TypeAliasType) and annotation not in step_locals:
92
+ discovered_names.add(annotation)
93
+ return
94
+
95
+ annotation_id = id(annotation)
96
+ if annotation_id in seen:
97
+ return
98
+ seen.add(annotation_id)
99
+
100
+ for arg in typing.get_args(annotation):
101
+ _collect(arg)
102
+
103
+ def _scan_callable(value: object) -> None:
104
+ target = value.func if isinstance(value, functools.partial) else value
105
+ try:
106
+ hints = typing.get_type_hints(target, localns=step_globals)
107
+ except Exception:
108
+ try:
109
+ signature = inspect.signature(value) # type: ignore[arg-type]
110
+ except (TypeError, ValueError):
111
+ return
112
+ hints = {}
113
+ for parameter in signature.parameters.values():
114
+ if parameter.annotation is not inspect.Parameter.empty:
115
+ hints[parameter.name] = parameter.annotation
116
+ if signature.return_annotation is not inspect.Signature.empty:
117
+ hints["return"] = signature.return_annotation
118
+
119
+ for annotation in hints.values():
120
+ _collect(annotation)
121
+
122
+ for value in step_locals.values():
123
+ if callable(value):
124
+ _scan_callable(value)
125
+
126
+ step_locals_keys = step_locals.keys()
127
+ for name in input_binding_names:
128
+ if name not in step_locals_keys and name in step_globals:
129
+ value = step_globals[name]
130
+ if callable(value):
131
+ _scan_callable(value)
132
+
133
+ return frozenset(discovered_names)
134
+
135
+
77
136
  def _build_step_globals(
78
137
  python_globals: dict[str, object],
79
138
  ) -> dict[str, object]:
@@ -266,6 +325,11 @@ class Runner:
266
325
 
267
326
  binding_commit_targets = set(output_binding_names)
268
327
  read_binding_names = frozenset(input_binding_names) - binding_commit_targets
328
+ implicit_type_reference_names = _discover_implicit_type_alias_reference_names(
329
+ step_locals=step_locals,
330
+ step_globals=step_globals,
331
+ input_binding_names=input_binding_names,
332
+ )
269
333
 
270
334
  step_context = StepContext(
271
335
  step_id=_build_step_id(caller_frame=caller_frame),
@@ -273,6 +337,7 @@ class Runner:
273
337
  step_locals=step_locals,
274
338
  binding_commit_targets=binding_commit_targets,
275
339
  read_binding_names=read_binding_names,
340
+ implicit_type_reference_names=implicit_type_reference_names,
276
341
  binding_name_to_type=binding_name_to_type,
277
342
  tool_result_rendering_policy=tool_result_rendering_policy,
278
343
  )
@@ -47,6 +47,7 @@ class StepContext:
47
47
 
48
48
  binding_commit_targets: set[str]
49
49
  read_binding_names: frozenset[str]
50
+ implicit_type_reference_names: frozenset[str]
50
51
 
51
52
  # Ordinary user-provided binding (for example a global named "memory") may exist in step_locals.
52
53
 
@@ -69,7 +69,7 @@ def _build_step_call(
69
69
  binding_names: list[str],
70
70
  allowed_step_kinds: tuple[str, ...],
71
71
  ) -> StepCall:
72
- referenced_global_names = step_context.read_binding_names - step_context.step_locals.keys()
72
+ referenced_global_names = (step_context.read_binding_names | step_context.implicit_type_reference_names) - step_context.step_locals.keys()
73
73
  filtered_globals = {name: step_context.step_globals[name] for name in referenced_global_names if name in step_context.step_globals}
74
74
  return StepCall(
75
75
  natural_program=processed_natural_program,
@@ -7,7 +7,7 @@ from pydantic_ai import RunContext
7
7
  from pydantic_ai.tools import Tool
8
8
 
9
9
  from ..runtime.step_context import StepContext
10
- from .assignment import assign_tool, eval_expression
10
+ from .assignment import assign_tool_async, eval_expression_async
11
11
  from .contracts import ToolBoundaryError
12
12
 
13
13
 
@@ -17,9 +17,9 @@ class ProvidedToolDefinition:
17
17
  tool: Tool[StepContext]
18
18
 
19
19
 
20
- def _eval_expression_or_raise(run_context: RunContext[StepContext], expression: str) -> object:
20
+ async def _eval_expression_or_raise_async(run_context: RunContext[StepContext], expression: str) -> object:
21
21
  try:
22
- return eval_expression(run_context.deps, expression)
22
+ return await eval_expression_async(run_context.deps, expression)
23
23
  except Exception as exception:
24
24
  raise ToolBoundaryError(kind="execution", message=str(exception), guidance="Fix the expression and retry.") from exception
25
25
 
@@ -27,19 +27,19 @@ def _eval_expression_or_raise(run_context: RunContext[StepContext], expression:
27
27
  def build_provided_tool_definitions() -> list[ProvidedToolDefinition]:
28
28
  metadata = {"nighthawk.provided": True}
29
29
 
30
- def nh_assign(
30
+ async def nh_assign(
31
31
  run_context: RunContext[StepContext],
32
32
  target_path: str,
33
33
  expression: str,
34
34
  ) -> dict[str, Any]:
35
- return assign_tool(
35
+ return await assign_tool_async(
36
36
  run_context.deps,
37
37
  target_path,
38
38
  expression,
39
39
  )
40
40
 
41
- def nh_eval(run_context: RunContext[StepContext], expression: str) -> object:
42
- return _eval_expression_or_raise(run_context, expression)
41
+ async def nh_eval(run_context: RunContext[StepContext], expression: str) -> object:
42
+ return await _eval_expression_or_raise_async(run_context, expression)
43
43
 
44
44
  return [
45
45
  ProvidedToolDefinition(
@@ -300,6 +300,7 @@ def test_codex_model_contract_calls_tool_via_mcp(tmp_path: Path) -> None:
300
300
  step_locals={},
301
301
  binding_commit_targets=set(),
302
302
  read_binding_names=frozenset(),
303
+ implicit_type_reference_names=frozenset(),
303
304
  )
304
305
 
305
306
  from typing import cast
@@ -67,6 +67,7 @@ def _build_prompt(
67
67
  step_locals=python_locals,
68
68
  binding_commit_targets=set(),
69
69
  read_binding_names=frozenset(),
70
+ implicit_type_reference_names=frozenset(),
70
71
  )
71
72
  return build_user_prompt(
72
73
  processed_natural_program=processed_natural_program,
@@ -2,7 +2,10 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ from collections.abc import Iterable
6
+
5
7
  import nighthawk as nh
8
+ from nighthawk.runtime.runner import _discover_implicit_type_alias_reference_names
6
9
  from nighthawk.runtime.step_context import StepContext
7
10
  from nighthawk.runtime.step_executor import build_user_prompt
8
11
 
@@ -27,13 +30,24 @@ class FakeAgent:
27
30
  return FakeRunResult(StepFinalResult(result=PassStepOutcome(kind="pass")))
28
31
 
29
32
 
30
- def build_step_context(*, python_globals: dict[str, object], python_locals: dict[str, object]) -> StepContext:
33
+ def build_step_context(
34
+ *,
35
+ python_globals: dict[str, object],
36
+ python_locals: dict[str, object],
37
+ input_binding_names: Iterable[str] = (),
38
+ ) -> StepContext:
39
+ implicit_type_reference_names = _discover_implicit_type_alias_reference_names(
40
+ step_locals=python_locals,
41
+ step_globals=python_globals,
42
+ input_binding_names=input_binding_names,
43
+ )
31
44
  return StepContext(
32
45
  step_id="test",
33
46
  step_globals=python_globals,
34
47
  step_locals=python_locals,
35
48
  binding_commit_targets=set(),
36
49
  read_binding_names=frozenset(),
50
+ implicit_type_reference_names=implicit_type_reference_names,
37
51
  )
38
52
 
39
53