nighthawk-python 0.2.0__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/.claude/rules/docs.md +2 -0
  2. nighthawk_python-0.3.0/.claude/settings.json +5 -0
  3. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/CHANGELOG.md +13 -1
  4. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/CONTRIBUTING.md +17 -0
  5. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/PKG-INFO +1 -1
  6. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/docs/for-coding-agents.md +3 -33
  7. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/pyproject.toml +1 -1
  8. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/src/nighthawk/runtime/scoping.py +24 -2
  9. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/src/nighthawk/runtime/step_executor.py +11 -25
  10. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/tests/public/test_public_api.py +42 -1
  11. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/.claude/rules/coding.md +0 -0
  12. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/.claude/unset_envs.sh +0 -0
  13. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/.devcontainer/Dockerfile.devcontainer +0 -0
  14. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/.devcontainer/Dockerfile.litellm +0 -0
  15. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/.devcontainer/devcontainer.json +0 -0
  16. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/.devcontainer/docker-compose.yaml +0 -0
  17. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/.devcontainer/litellm-config.yaml +0 -0
  18. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/.github/dependabot.yml +0 -0
  19. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/.github/workflows/ci.yml +0 -0
  20. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/.github/workflows/docs.yml +0 -0
  21. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/.github/workflows/publish.yml +0 -0
  22. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/.gitignore +0 -0
  23. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/.python-version +0 -0
  24. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/AGENTS.md +0 -0
  25. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/CLAUDE.md +0 -0
  26. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/LICENSE +0 -0
  27. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/README.md +0 -0
  28. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/docs/api.md +0 -0
  29. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/docs/assets/nighthawk_logo-128x128.png +0 -0
  30. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/docs/coding-agent-backends.md +0 -0
  31. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/docs/design.md +0 -0
  32. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/docs/index.md +0 -0
  33. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/docs/providers.md +0 -0
  34. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/docs/quickstart.md +0 -0
  35. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/docs/roadmap.md +0 -0
  36. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/docs/tutorial.md +0 -0
  37. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/mkdocs.yml +0 -0
  38. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/pyrightconfig.json +0 -0
  39. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/src/nighthawk/__init__.py +0 -0
  40. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/src/nighthawk/backends/__init__.py +0 -0
  41. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/src/nighthawk/backends/base.py +0 -0
  42. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/src/nighthawk/backends/claude_code_cli.py +0 -0
  43. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/src/nighthawk/backends/claude_code_sdk.py +0 -0
  44. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/src/nighthawk/backends/codex.py +0 -0
  45. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/src/nighthawk/backends/mcp_boundary.py +0 -0
  46. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/src/nighthawk/backends/mcp_server.py +0 -0
  47. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/src/nighthawk/backends/tool_bridge.py +0 -0
  48. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/src/nighthawk/configuration.py +0 -0
  49. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/src/nighthawk/errors.py +0 -0
  50. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/src/nighthawk/identifier_path.py +0 -0
  51. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/src/nighthawk/json_renderer.py +0 -0
  52. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/src/nighthawk/natural/__init__.py +0 -0
  53. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/src/nighthawk/natural/blocks.py +0 -0
  54. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/src/nighthawk/natural/decorator.py +0 -0
  55. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/src/nighthawk/natural/transform.py +0 -0
  56. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/src/nighthawk/runtime/__init__.py +0 -0
  57. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/src/nighthawk/runtime/async_bridge.py +0 -0
  58. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/src/nighthawk/runtime/prompt.py +0 -0
  59. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/src/nighthawk/runtime/runner.py +0 -0
  60. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/src/nighthawk/runtime/step_context.py +0 -0
  61. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/src/nighthawk/runtime/step_contract.py +0 -0
  62. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/src/nighthawk/runtime/tool_calls.py +0 -0
  63. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/src/nighthawk/tools/__init__.py +0 -0
  64. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/src/nighthawk/tools/assignment.py +0 -0
  65. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/src/nighthawk/tools/contracts.py +0 -0
  66. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/src/nighthawk/tools/execution.py +0 -0
  67. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/src/nighthawk/tools/provided.py +0 -0
  68. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/src/nighthawk/tools/registry.py +0 -0
  69. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/tests/__init__.py +0 -0
  70. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/tests/backends/__init__.py +0 -0
  71. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/tests/backends/test_claude_code_cli.py +0 -0
  72. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/tests/backends/test_claude_code_sdk.py +0 -0
  73. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/tests/backends/test_codex.py +0 -0
  74. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/tests/conftest.py +0 -0
  75. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/tests/docs/__init__.py +0 -0
  76. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/tests/docs/test_prompt_examples.py +0 -0
  77. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/tests/execution/__init__.py +0 -0
  78. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/tests/execution/prompt_test_helpers.py +0 -0
  79. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/tests/execution/stub_executor.py +0 -0
  80. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/tests/execution/test_execution_outcome_prompt_fragment.py +0 -0
  81. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/tests/execution/test_globals_prompt.py +0 -0
  82. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/tests/execution/test_natural_block_ordering.py +0 -0
  83. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/tests/execution/test_natural_traceback.py +0 -0
  84. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/tests/execution/test_runtime.py +0 -0
  85. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/tests/execution/test_variables_prompt.py +0 -0
  86. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/tests/integration/__init__.py +0 -0
  87. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/tests/integration/skip_helpers.py +0 -0
  88. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/tests/integration/test_carry_pattern.py +0 -0
  89. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/tests/integration/test_claude_code_cli_integration.py +0 -0
  90. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/tests/integration/test_claude_code_sdk_integration.py +0 -0
  91. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/tests/integration/test_codex_integration.py +0 -0
  92. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/tests/integration/test_llm_integration.py +0 -0
  93. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/tests/natural/__init__.py +0 -0
  94. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/tests/natural/test_blocks.py +0 -0
  95. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/tests/public/__init__.py +0 -0
  96. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/tests/public/test_readme_example.py +0 -0
  97. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/tests/test_renderer.py +0 -0
  98. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/tests/tools/__init__.py +0 -0
  99. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/tests/tools/test_assignment_async.py +0 -0
  100. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/tests/tools/test_contracts.py +0 -0
  101. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/tests/tools/test_registry.py +0 -0
  102. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/tests/tools/test_tool_boundary.py +0 -0
  103. {nighthawk_python-0.2.0 → nighthawk_python-0.3.0}/uv.lock +0 -0
@@ -91,6 +91,8 @@ Each file has a distinct audience and scope. Content belongs in exactly one file
91
91
  - This file should be self-contained: a coding agent reading only this file should be able to write correct Nighthawk code without consulting other docs.
92
92
  - This file is consumed standalone (`@docs/for-coding-agents.md` in CLAUDE.md/AGENTS.md, GitHub raw URL, etc.). Do not assume sibling files exist at relative paths.
93
93
  - All external references to other docs use absolute URLs based on `site_url` from `mkdocs.yml` (currently `https://kurusugawa-computer.github.io/nighthawk-python/`). If `site_url` changes, update the URLs in this file.
94
+ - `@nh.tool` is deprecated. Do not add examples, recommendations, or references to `@nh.tool` in this file. Binding functions are the only recommended callable exposure mechanism.
95
+ - Filter content for coding-agent relevance. Omit infrastructure-level concerns (scoped overrides parameter lists, exception hierarchy beyond `ExecutionError`, observability/tracing) that do not affect how an agent writes Natural blocks or binding functions. Mention existence and link to Tutorial or Design for details.
94
96
 
95
97
  ### api.md specifics
96
98
 
@@ -0,0 +1,5 @@
1
+ {
2
+ "enabledPlugins": {
3
+ "pyright-lsp@claude-plugins-official": true
4
+ }
5
+ }
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.0] - 2026-03-18
11
+
12
+ ### Added
13
+ - `system_prompt_suffix_fragment_scope` and `user_prompt_suffix_fragment_scope` context managers for lightweight prompt fragment management without full scope overhead.
14
+ - OpenTelemetry tracer now reports `instrumenting_library_version`.
15
+
16
+ ### Changed
17
+ - Simplified OpenTelemetry span hierarchy: removed implicit `nighthawk.scope` spans and `nighthawk.step_executor` spans. `nighthawk.scope` spans are now emitted only for explicit `nh.scope()` calls.
18
+ - `nighthawk.run` span no longer includes `scope.id` attribute; only `run.id` is emitted.
19
+ - Trimmed `for-coding-agents.md` for coding-agent relevance: removed deprecated `@nh.tool` references, condensed exception hierarchy, scoped overrides, and added debugging context to `StepContextLimits`.
20
+
10
21
  ## [0.2.0] - 2026-03-16
11
22
 
12
23
  ### Added
@@ -30,6 +41,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
30
41
  - Step executor abstraction and provider integration foundation.
31
42
  - Core documentation and project scaffolding.
32
43
 
33
- [Unreleased]: https://github.com/kurusugawa-computer/nighthawk-python/compare/v0.2.0...HEAD
44
+ [Unreleased]: https://github.com/kurusugawa-computer/nighthawk-python/compare/v0.3.0...HEAD
45
+ [0.3.0]: https://github.com/kurusugawa-computer/nighthawk-python/compare/v0.2.0...v0.3.0
34
46
  [0.2.0]: https://github.com/kurusugawa-computer/nighthawk-python/compare/v0.1.0...v0.2.0
35
47
  [0.1.0]: https://github.com/kurusugawa-computer/nighthawk-python/tree/v0.1.0
@@ -53,6 +53,23 @@ Then run with your `.env` loaded:
53
53
  set -a; source .env; set +a; uv run pytest -q
54
54
  ```
55
55
 
56
+ ### Trace inspection with otel-tui
57
+
58
+ Nighthawk emits OpenTelemetry traces for step execution. You can inspect them
59
+ locally using [otel-tui](https://github.com/ymtdzzz/otel-tui):
60
+
61
+ ```bash
62
+ docker run --rm -it -p 4318:4318 --name otel-tui ymtdzzz/otel-tui:latest
63
+ ```
64
+
65
+ Then run integration tests with the collector endpoint:
66
+
67
+ ```bash
68
+ OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 uv run pytest -q tests/integration/
69
+ ```
70
+
71
+ Traces appear in the otel-tui terminal UI in real time.
72
+
56
73
  ### Environment variables
57
74
 
58
75
  - `OPENAI_API_KEY`: Required for OpenAI integration tests (also requires `pydantic-ai-slim[openai]`).
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nighthawk-python
3
- Version: 0.2.0
3
+ Version: 0.3.0
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/
@@ -64,10 +64,6 @@ Each Natural block should make exactly one independent judgment. If a block make
64
64
 
65
65
  Binding functions (local or module-level callables) are the preferred way to expose functions to the LLM. The LLM discovers them from the LOCALS/GLOBALS sections of the prompt, rendered as their signature with the first docstring line as `# intent:`.
66
66
 
67
- ### Prefer binding functions over `@nh.tool`
68
-
69
- `@nh.tool` is reserved strictly for cases requiring `RunContext[StepContext]` access. Binding functions incur no per-definition token overhead beyond a signature line. Always use binding functions unless `RunContext` access is needed.
70
-
71
67
  ### Keep locals minimal
72
68
 
73
69
  Module-level names that are stable across invocations (constants, classes, utility functions) should stay in GLOBALS via `<name>` read bindings. Reserve function parameters for data that genuinely varies per call.
@@ -156,11 +152,7 @@ except nh.ExecutionError as e:
156
152
  print(f"Validation failed: {e}")
157
153
  ```
158
154
 
159
- Custom exception types referenced in step locals or globals are available as raise targets.
160
-
161
- ### Exception hierarchy
162
-
163
- All exceptions inherit from `NighthawkError`: `ExecutionError`, `NaturalParseError`, `ToolEvaluationError`, `ToolValidationError`, `ToolRegistrationError`.
155
+ Custom exception types referenced in step locals or globals are available as raise targets. Catch `nh.ExecutionError` for Natural block failures; all Nighthawk exceptions inherit from `nh.NighthawkError`.
164
156
 
165
157
  ## 6. Cross-block composition
166
158
 
@@ -221,30 +213,9 @@ with nh.run(step_executor):
221
213
  result = my_natural_function(data)
222
214
  ```
223
215
 
224
- ### Scoped overrides
225
-
226
- Use `nh.scope()` to override execution settings within an existing run. Each scope generates a new `scope_id` while keeping the current `run_id`.
227
-
228
- Parameters:
229
-
230
- - `step_executor_configuration`: replace the entire configuration.
231
- - `step_executor_configuration_patch`: partially override specific fields.
232
- - `step_executor`: replace the step executor entirely.
233
- - `system_prompt_suffix_fragment`: append text to the system prompt for the scope.
234
- - `user_prompt_suffix_fragment`: append text to the user prompt for the scope.
235
-
236
- ```python
237
- with nh.scope(
238
- step_executor_configuration_patch=nh.StepExecutorConfigurationPatch(
239
- model="openai-responses:gpt-5-mini",
240
- ),
241
- ):
242
- expensive_analysis(data)
243
- ```
244
-
245
- ### Context limits
216
+ Use `nh.scope()` to override model, prompts, or context limits within an existing run. For details, see [Tutorial](https://kurusugawa-computer.github.io/nighthawk-python/tutorial/).
246
217
 
247
- LOCALS and GLOBALS sections are bounded by `StepContextLimits`. Configure via `StepExecutorConfiguration`:
218
+ LOCALS and GLOBALS sections are bounded by `StepContextLimits`. When bindings are missing or truncated (`<snipped>`), adjust the limits:
248
219
 
249
220
  ```python
250
221
  configuration = nh.StepExecutorConfiguration(
@@ -304,7 +275,6 @@ def judge_review(review_data: str | nh.JsonableValue) -> ReviewVerdict:
304
275
  | Use `<:carry>` (write binding) for mutable context | Rebinding breaks the caller's reference | Use `<carry>` (read binding); mutate in-place |
305
276
  | Put two independent judgments in one block | Non-deterministic, hard to test, unclear contract | Split into two blocks connected by Python |
306
277
  | Use Natural for deterministic computation | Wastes latency/cost, adds non-determinism | Use Python |
307
- | Use `@nh.tool` when a binding function suffices | Unnecessary per-definition token overhead | Use binding functions; reserve `@nh.tool` for `RunContext[StepContext]` access |
308
278
  | Forget type annotations on write bindings | No validation or coercion at commit time | Always annotate `<:name>` bindings |
309
279
  | Duplicate module-level constants as function parameters | Moves stable values from GLOBALS to LOCALS, wastes tokens | Reference via `<name>` read binding |
310
280
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nighthawk-python"
3
- version = "0.2.0"
3
+ version = "0.3.0"
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"
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import importlib.metadata
3
4
  import uuid
4
5
  from collections.abc import Iterator
5
6
  from contextlib import contextmanager
@@ -36,7 +37,9 @@ STEP_ID = "step.id"
36
37
  TOOL_CALL_ID = "tool_call.id"
37
38
 
38
39
 
39
- _tracer = get_tracer_provider().get_tracer("nighthawk")
40
+ _LIBRARY_VERSION = importlib.metadata.version("nighthawk-python")
41
+
42
+ _tracer = get_tracer_provider().get_tracer("nighthawk", _LIBRARY_VERSION)
40
43
 
41
44
 
42
45
  @contextmanager
@@ -108,6 +111,26 @@ def get_user_prompt_suffix_fragments() -> tuple[str, ...]:
108
111
  return _user_prompt_suffix_fragments_var.get()
109
112
 
110
113
 
114
+ @contextmanager
115
+ def system_prompt_suffix_fragment_scope(fragment: str) -> Iterator[None]:
116
+ current = _system_prompt_suffix_fragments_var.get()
117
+ token = _system_prompt_suffix_fragments_var.set((*current, fragment))
118
+ try:
119
+ yield
120
+ finally:
121
+ _system_prompt_suffix_fragments_var.reset(token)
122
+
123
+
124
+ @contextmanager
125
+ def user_prompt_suffix_fragment_scope(fragment: str) -> Iterator[None]:
126
+ current = _user_prompt_suffix_fragments_var.get()
127
+ token = _user_prompt_suffix_fragments_var.set((*current, fragment))
128
+ try:
129
+ yield
130
+ finally:
131
+ _user_prompt_suffix_fragments_var.reset(token)
132
+
133
+
111
134
  def _resolve_agent_step_executor(step_executor: StepExecutor) -> AgentStepExecutor:
112
135
  from .step_executor import AgentStepExecutor
113
136
 
@@ -179,7 +202,6 @@ def run(
179
202
  "nighthawk.run",
180
203
  **{
181
204
  RUN_ID: execution_context.run_id,
182
- SCOPE_ID: execution_context.scope_id,
183
205
  },
184
206
  ):
185
207
  yield
@@ -13,13 +13,8 @@ from ..tools.registry import get_visible_tools
13
13
  from .async_bridge import run_coroutine_synchronously
14
14
  from .prompt import build_user_prompt, extract_references_and_program
15
15
  from .scoping import (
16
- RUN_ID,
17
- SCOPE_ID,
18
- STEP_ID,
19
- get_execution_context,
20
16
  get_system_prompt_suffix_fragments,
21
- scope,
22
- span,
17
+ system_prompt_suffix_fragment_scope,
23
18
  )
24
19
  from .step_context import (
25
20
  _MISSING,
@@ -315,25 +310,16 @@ class AgentStepExecutor:
315
310
  allowed_step_kinds=allowed_step_kinds,
316
311
  )
317
312
 
318
- with scope(system_prompt_suffix_fragment=step_system_prompt_fragment):
319
- execution_context = get_execution_context()
320
- with (
321
- span(
322
- "nighthawk.step_executor",
323
- **{
324
- RUN_ID: execution_context.run_id,
325
- SCOPE_ID: execution_context.scope_id,
326
- STEP_ID: step_context.step_id,
327
- },
328
- ),
329
- step_context_scope(step_context),
330
- ):
331
- result = await self._run_agent(
332
- user_prompt=user_prompt,
333
- step_context=step_context,
334
- toolset=toolset,
335
- structured_output_type=structured_output_type,
336
- )
313
+ with (
314
+ system_prompt_suffix_fragment_scope(step_system_prompt_fragment),
315
+ step_context_scope(step_context),
316
+ ):
317
+ result = await self._run_agent(
318
+ user_prompt=user_prompt,
319
+ step_context=step_context,
320
+ toolset=toolset,
321
+ structured_output_type=structured_output_type,
322
+ )
337
323
 
338
324
  step_outcome = self._parse_agent_result(result)
339
325
  bindings = self._extract_bindings(binding_names=binding_names, step_context=step_context)
@@ -1,4 +1,5 @@
1
1
  import asyncio
2
+ import importlib.metadata
2
3
  from collections.abc import Generator
3
4
 
4
5
  import pytest
@@ -26,7 +27,7 @@ def step_span_exporter() -> Generator[InMemorySpanExporter, None, None]:
26
27
  tracer_provider.add_span_processor(SimpleSpanProcessor(span_exporter))
27
28
 
28
29
  previous_tracer_provider = runtime_scoping._tracer
29
- runtime_scoping._tracer = tracer_provider.get_tracer("nighthawk")
30
+ runtime_scoping._tracer = tracer_provider.get_tracer("nighthawk", importlib.metadata.version("nighthawk-python"))
30
31
  try:
31
32
  yield span_exporter
32
33
  finally:
@@ -428,3 +429,43 @@ def test_step_span_failure_event_structure_is_compact(step_span_exporter: InMemo
428
429
  assert "exception" in exception_event_name_set
429
430
  assert "nighthawk.step.failed" in exception_event_name_set
430
431
  assert len(step_span.events) == 2
432
+
433
+
434
+ def test_run_span_has_only_run_id_attribute(step_span_exporter: InMemorySpanExporter) -> None:
435
+ with nh.run(StubExecutor(), run_id="attr-test"):
436
+
437
+ @nh.natural_function
438
+ def natural_value_function() -> int:
439
+ """natural
440
+ <:result>
441
+ {"step_outcome": {"kind": "pass"}, "bindings": {"result": 42}}
442
+ """
443
+ return result # noqa: F821 # pyright: ignore[reportUndefinedVariable]
444
+
445
+ assert natural_value_function() == 42
446
+
447
+ run_span_list = [s for s in step_span_exporter.get_finished_spans() if s.name == "nighthawk.run"]
448
+ assert len(run_span_list) == 1
449
+ run_span = run_span_list[0]
450
+ assert _require_attribute_key_set(run_span) == {"run.id"}
451
+ assert _require_attribute_value(run_span, "run.id") == "attr-test"
452
+
453
+
454
+ def test_no_implicit_scope_or_step_executor_spans(step_span_exporter: InMemorySpanExporter) -> None:
455
+ with nh.run(StubExecutor()):
456
+
457
+ @nh.natural_function
458
+ def natural_value_function() -> int:
459
+ """natural
460
+ <:result>
461
+ {"step_outcome": {"kind": "pass"}, "bindings": {"result": 7}}
462
+ """
463
+ return result # noqa: F821 # pyright: ignore[reportUndefinedVariable]
464
+
465
+ assert natural_value_function() == 7
466
+
467
+ span_name_set = {s.name for s in step_span_exporter.get_finished_spans()}
468
+ assert "nighthawk.scope" not in span_name_set
469
+ assert "nighthawk.step_executor" not in span_name_set
470
+ assert "nighthawk.run" in span_name_set
471
+ assert "nighthawk.step" in span_name_set