nighthawk-python 0.15.0__py3-none-any.whl → 0.16.0__py3-none-any.whl

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/composition.py CHANGED
@@ -1,4 +1,4 @@
1
- """Explicit inheritance and composition operations for execution scopes."""
1
+ """Omitted arguments and explicit collection composition operations."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
@@ -30,7 +30,11 @@ class UnsetType:
30
30
 
31
31
 
32
32
  UNSET = UnsetType()
33
- """Inherit the enclosing scope value; also marks omitted rewrite fields."""
33
+ """An omitted argument: use the receiving API's documented default behavior.
34
+
35
+ Scopes inherit, runs create fresh resources, and rewrites retain the current field.
36
+ Ordinary values, including empty collections, remain explicit values.
37
+ """
34
38
 
35
39
 
36
40
  @dataclass(frozen=True, init=False)
@@ -121,8 +121,9 @@ class StepExecutorConfiguration(BaseModel):
121
121
  Instances retain identity; the host owns client lifetime. Serialization
122
122
  must explicitly exclude a live model field, including in nested records.
123
123
  Configuration representations omit the model to avoid traversing credentials.
124
- model_settings: Provider-specific model settings. Accepts a dict or a
125
- backend-specific BaseModel instance (auto-converted to dict).
124
+ model_settings: Provider-specific overrides. Defaults to an empty dict;
125
+ accepts a dict or a backend-specific BaseModel (converted to dict).
126
+ None and UNSET are invalid. An empty dict preserves model-owned defaults.
126
127
  prompts: Prompt templates for step execution.
127
128
  context_limits: Token and item limits for context rendering.
128
129
  json_renderer_style: Headson rendering style for JSON summarization.
@@ -136,11 +137,11 @@ class StepExecutorConfiguration(BaseModel):
136
137
  model_config = ConfigDict(extra="forbid", frozen=True)
137
138
 
138
139
  model: str | InstanceOf[Model] = Field(default="openai-responses:gpt-5.6-luna", repr=False)
139
- model_settings: dict[str, Any] | BaseModel | None = None
140
+ model_settings: dict[str, Any] | BaseModel = Field(default_factory=dict)
140
141
 
141
142
  @field_validator("model_settings", mode="before")
142
143
  @classmethod
143
- def _normalize_model_settings(cls, value: Any) -> dict[str, Any] | None:
144
+ def _normalize_model_settings(cls, value: Any) -> dict[str, Any]:
144
145
  if isinstance(value, BaseModel):
145
146
  return value.model_dump()
146
147
  return value
@@ -10,8 +10,9 @@ from contextlib import contextmanager, nullcontext
10
10
  from contextvars import ContextVar
11
11
  from dataclasses import dataclass
12
12
  from types import MappingProxyType
13
- from typing import Any, Protocol
13
+ from typing import Any, Protocol, runtime_checkable
14
14
 
15
+ from .composition import UNSET, UnsetType
15
16
  from .configuration import ModelSelection
16
17
  from .errors import NighthawkError
17
18
  from .natural.definition import NaturalFunctionDefinition
@@ -31,6 +32,7 @@ class FunctionCall:
31
32
  model_selection: ModelSelection | None
32
33
 
33
34
 
35
+ @runtime_checkable
34
36
  class FunctionCallHandler(Protocol):
35
37
  """Application-owned admission, grouping, and observation of function calls."""
36
38
 
@@ -48,9 +50,19 @@ _call: ContextVar[FunctionCall | None] = ContextVar("nighthawk_function_call", d
48
50
 
49
51
 
50
52
  @contextmanager
51
- def function_call_scope(handler: FunctionCallHandler | None) -> Iterator[None]:
52
- """Install or explicitly clear interception independently of executor runs."""
53
- token = _handler.set(handler)
53
+ def function_call_scope(handler: FunctionCallHandler | None | UnsetType = UNSET) -> Iterator[None]:
54
+ """Inherit, replace, or clear interception independently of executor runs.
55
+
56
+ Omission or UNSET inherits; None clears; a handler replaces the current one.
57
+ Invalid handlers fail before context installation.
58
+ """
59
+ if (
60
+ not isinstance(handler, UnsetType)
61
+ and handler is not None
62
+ and (not isinstance(handler, FunctionCallHandler) or not callable(handler.invoke) or not callable(handler.invoke_async))
63
+ ):
64
+ raise TypeError("handler must implement FunctionCallHandler, be None, or be UNSET")
65
+ token = _handler.set(_handler.get() if isinstance(handler, UnsetType) else handler)
54
66
  try:
55
67
  yield
56
68
  finally:
nighthawk/oversight.py CHANGED
@@ -81,11 +81,12 @@ class Reject:
81
81
 
82
82
  @dataclass(frozen=True)
83
83
  class Rewrite:
84
- """Replace supplied commit fields; UNSET inherits and Return(None) returns None.
84
+ """Replace supplied commit fields; UNSET retains and Return(None) returns None.
85
85
 
86
86
  A mapping replaces all writes. Use outcome=Return(value=...) to supply
87
87
  a new return value or change another allowed outcome into a return.
88
88
  Replacements are validated before commit without replaying return expressions.
89
+ A resolved Raise cannot be paired with an explicit nonempty output mapping.
89
90
  """
90
91
 
91
92
  outcome: StepResult | UnsetType = UNSET
@@ -96,11 +97,13 @@ class Rewrite:
96
97
  if not isinstance(self.outcome, (UnsetType, Pass, Return, Break, Continue, Raise)):
97
98
  raise TypeError("Rewrite outcome must be a resolved StepResult or UNSET")
98
99
  if not isinstance(self.binding_name_to_value, UnsetType):
99
- if not isinstance(self.binding_name_to_value, Mapping):
100
- raise TypeError("Rewrite binding_name_to_value must be a mapping or UNSET")
100
+ if not isinstance(self.binding_name_to_value, Mapping) or any(not isinstance(name, str) for name in self.binding_name_to_value):
101
+ raise TypeError("Rewrite binding_name_to_value must be a string-keyed mapping or UNSET")
101
102
  object.__setattr__(self, "binding_name_to_value", _reference_mapping(self.binding_name_to_value))
102
103
  if self.outcome is UNSET and self.binding_name_to_value is UNSET:
103
104
  raise ValueError("Rewrite must change outcome or binding_name_to_value")
105
+ if isinstance(self.outcome, Raise) and not isinstance(self.binding_name_to_value, UnsetType) and len(self.binding_name_to_value):
106
+ raise ValueError("Rewrite Raise cannot include output bindings")
104
107
 
105
108
 
106
109
  type StepCommitDecision = Accept | Reject | Rewrite
@@ -5,13 +5,21 @@ from __future__ import annotations
5
5
  from collections.abc import Iterator, Mapping
6
6
  from dataclasses import dataclass
7
7
  from types import CellType, MappingProxyType
8
- from typing import Literal
8
+ from typing import Literal, Never
9
9
 
10
10
  from .bindings import MISSING_ANNOTATION
11
11
  from .description import annotation_text
12
12
  from .discovery import DiscoveryResult, page_names
13
13
 
14
- type EnvironmentSource = Literal["effective", "python", "parent", "task", "step"]
14
+ type EnvironmentSource = Literal["effective", "python", "task", "step"]
15
+
16
+ _EMPTY_NAME_TO_VALUE: Mapping[str, Never] = MappingProxyType({})
17
+
18
+
19
+ def _copy_name_to_value[T](name_to_value: Mapping[str, T], parameter: str) -> dict[str, T]:
20
+ if not isinstance(name_to_value, Mapping) or any(not isinstance(name, str) for name in name_to_value):
21
+ raise TypeError(f"{parameter} must be a string-keyed mapping")
22
+ return dict(name_to_value)
15
23
 
16
24
 
17
25
  class MissingEnvironmentNameError(NameError):
@@ -63,6 +71,16 @@ class _ExpressionNamespace(dict[str, object]):
63
71
  return self._override_name_to_value[name]
64
72
  return self._environment.resolve(name)
65
73
 
74
+ def __contains__(self, name: object) -> bool:
75
+ # Python 3.14 forward references check membership before looking up a name.
76
+ if not isinstance(name, str):
77
+ return False
78
+ try:
79
+ self[name]
80
+ except MissingEnvironmentNameError:
81
+ return False
82
+ return True
83
+
66
84
 
67
85
  @dataclass(frozen=True)
68
86
  class _RuntimeAccess:
@@ -78,7 +96,7 @@ class _RuntimeAccess:
78
96
  class StepEnvironment:
79
97
  """Read-only environment views with a separate, tracked working namespace.
80
98
 
81
- Parent membership and object associations are captured at entry.
99
+ Python working associations and scoped task references are captured at entry.
82
100
  Application values remain live; the views do not freeze their object graphs.
83
101
  """
84
102
 
@@ -88,28 +106,29 @@ class StepEnvironment:
88
106
  python_name_to_value: Mapping[str, object],
89
107
  python_globals: dict[str, object],
90
108
  local_name_set: frozenset[str] = frozenset(),
91
- python_name_to_cell: Mapping[str, CellType] | None = None,
92
- parent_name_to_value: Mapping[str, object] | None = None,
93
- task_name_to_value: Mapping[str, object] | None = None,
109
+ python_name_to_cell: Mapping[str, CellType] = _EMPTY_NAME_TO_VALUE,
110
+ task_name_to_value: Mapping[str, object] = _EMPTY_NAME_TO_VALUE,
94
111
  output_name_set: frozenset[str] = frozenset(),
95
- binding_name_to_annotation: Mapping[str, object] | None = None,
96
- binding_name_to_type: Mapping[str, object] | None = None,
112
+ binding_name_to_annotation: Mapping[str, object] = _EMPTY_NAME_TO_VALUE,
113
+ binding_name_to_type: Mapping[str, object] = _EMPTY_NAME_TO_VALUE,
97
114
  runtime_name_set: frozenset[str] = frozenset(),
98
115
  ) -> None:
116
+ python_name_to_value = _copy_name_to_value(python_name_to_value, "python_name_to_value")
117
+ if not isinstance(python_globals, dict) or any(not isinstance(name, str) for name in python_globals):
118
+ raise TypeError("python_globals must be a string-keyed dict")
99
119
  self._runtime_name_set = frozenset(runtime_name_set)
100
120
  self._python_name_to_value = {name: value for name, value in python_name_to_value.items() if not self._is_runtime_name(name)}
101
121
  self._python_global_name_to_value = python_globals
102
122
  builtins_value = python_globals.get("__builtins__", __builtins__)
103
123
  self._python_builtin_name_to_value: dict[str, object] = builtins_value if isinstance(builtins_value, dict) else vars(builtins_value)
104
124
  self._local_name_set = local_name_set
105
- self._python_name_to_cell = dict(python_name_to_cell or {})
125
+ self._python_name_to_cell = _copy_name_to_value(python_name_to_cell, "python_name_to_cell")
106
126
  self._output_name_set = output_name_set
107
127
  self._step_name_to_value: dict[str, object] = {}
108
- self._binding_name_to_annotation = dict(binding_name_to_annotation or {})
109
- self._binding_name_to_type = dict(binding_name_to_type or {})
128
+ self._binding_name_to_annotation = _copy_name_to_value(binding_name_to_annotation, "binding_name_to_annotation")
129
+ self._binding_name_to_type = _copy_name_to_value(binding_name_to_type, "binding_name_to_type")
110
130
  self.python_name_to_value: Mapping[str, object] = _PythonEnvironmentView(self)
111
- self.parent_name_to_value = MappingProxyType(dict(parent_name_to_value or {}))
112
- self.task_name_to_value = MappingProxyType(dict(task_name_to_value or {}))
131
+ self.task_name_to_value = MappingProxyType(_copy_name_to_value(task_name_to_value, "task_name_to_value"))
113
132
  self.step_name_to_value = MappingProxyType(self._step_name_to_value)
114
133
  known_names = (
115
134
  python_name_to_value.keys()
@@ -117,7 +136,6 @@ class StepEnvironment:
117
136
  | self._python_builtin_name_to_value.keys()
118
137
  | self._local_name_set
119
138
  | self._python_name_to_cell.keys()
120
- | self.parent_name_to_value.keys()
121
139
  | self.task_name_to_value.keys()
122
140
  | self._runtime_name_set
123
141
  )
@@ -166,7 +184,6 @@ class StepEnvironment:
166
184
  source_to_name_to_value = {
167
185
  "step": self.step_name_to_value,
168
186
  "python": self.python_name_to_value,
169
- "parent": self.parent_name_to_value,
170
187
  "task": self.task_name_to_value,
171
188
  }
172
189
  if source != "effective" and source not in source_to_name_to_value:
@@ -188,19 +205,25 @@ class StepEnvironment:
188
205
  self._step_name_to_value[name] = value
189
206
 
190
207
  def snapshot_working_state(self) -> dict[str, object]:
191
- snapshot = dict(self.parent_name_to_value)
208
+ """Copy this step's bound working names for explicit application-owned sharing.
209
+
210
+ Include Python locals, live cells, and tracked assignments.
211
+ Exclude module globals, builtins, scoped task references, and runtime names.
212
+ Referenced objects retain identity; the returned mapping is independent.
213
+ """
214
+ name_to_value: dict[str, object] = {}
192
215
  for name in self._python_name_to_value.keys() | self._local_name_set | self._python_name_to_cell.keys():
193
216
  if self._is_runtime_name(name):
194
217
  continue
195
218
  try:
196
- snapshot[name] = self._resolve_python(name)
219
+ name_to_value[name] = self._resolve_python(name)
197
220
  except (KeyError, UnboundLocalError):
198
- snapshot.pop(name, None)
199
- snapshot.update(self.step_name_to_value)
200
- return snapshot
221
+ continue
222
+ name_to_value.update(self.step_name_to_value)
223
+ return name_to_value
201
224
 
202
- def expression_namespace(self, overrides: Mapping[str, object] | None = None) -> dict[str, object]:
203
- return _ExpressionNamespace(self, overrides or {})
225
+ def expression_namespace(self, overrides: Mapping[str, object] = _EMPTY_NAME_TO_VALUE) -> dict[str, object]:
226
+ return _ExpressionNamespace(self, _copy_name_to_value(overrides, "overrides"))
204
227
 
205
228
  def reference(self, name: str, *, source: EnvironmentSource = "effective") -> str:
206
229
  """Return a Python expression resolving this binding when evaluated."""
@@ -208,18 +231,17 @@ class StepEnvironment:
208
231
 
209
232
  def discover(self, *, source: EnvironmentSource = "effective", cursor: str | None = None, max_items: int = 8) -> dict[str, object]:
210
233
  """List names with provenance, usable references, and progressing pages."""
211
- source_to_view = {
234
+ source_to_name_to_value = {
212
235
  "python": self.python_name_to_value,
213
- "parent": self.parent_name_to_value,
214
236
  "task": self.task_name_to_value,
215
237
  "step": self.step_name_to_value,
216
238
  }
217
- if source != "effective" and source not in source_to_view:
218
- return {"error": "Unknown source", "recovery": "Choose effective, python, parent, task, or step."}
239
+ if source != "effective" and source not in source_to_name_to_value:
240
+ return {"error": "Unknown source", "recovery": "Choose effective, python, task, or step."}
219
241
  names = (
220
- tuple(sorted(set().union(*[set(view) for view in source_to_view.values()])))
242
+ tuple(sorted(set().union(*[set(view) for view in source_to_name_to_value.values()])))
221
243
  if source == "effective"
222
- else tuple(sorted(source_to_view[source]))
244
+ else tuple(sorted(source_to_name_to_value[source]))
223
245
  )
224
246
  page = page_names(names, source=source, cursor=cursor, max_items=max_items)
225
247
  if page.error is not None:
@@ -244,7 +266,6 @@ class StepEnvironment:
244
266
  for source, view in (
245
267
  ("step", self.step_name_to_value),
246
268
  ("python", self.python_name_to_value),
247
- ("parent", self.parent_name_to_value),
248
269
  ("task", self.task_name_to_value),
249
270
  )
250
271
  if name in view
@@ -38,7 +38,7 @@ async def evaluate_compiled_expression(
38
38
  allow_awaitable_result: bool = True,
39
39
  override_name_to_value: Mapping[str, object] | None = None,
40
40
  ) -> object:
41
- namespace = step_context.environment.expression_namespace(override_name_to_value)
41
+ namespace = step_context.environment.expression_namespace({} if override_name_to_value is None else override_name_to_value)
42
42
  try:
43
43
  value = eval(compiled, namespace, namespace)
44
44
  if inspect.isawaitable(value):
@@ -2,6 +2,7 @@
2
2
 
3
3
  from collections.abc import Mapping
4
4
 
5
+ from ..composition import UNSET, UnsetType
5
6
  from ..errors import ExecutionError
6
7
  from .expression import evaluate_expression_operation
7
8
  from .operation_contract import OperationError
@@ -13,14 +14,21 @@ from .step_result import Break, Continue, Pass, Raise, Return, StepResult
13
14
  async def resolve_model_candidate(
14
15
  request: StepRequest,
15
16
  outcome: StepOutcome,
16
- binding_name_to_value: Mapping[str, object],
17
+ binding_name_to_value: Mapping[str, object] | UnsetType = UNSET,
17
18
  ) -> StepCandidate:
18
19
  """Resolve a provider response once; leave commit validation to the runner."""
19
20
  if outcome.kind not in request.allowed_step_kinds:
20
21
  raise ExecutionError(f"Step '{outcome.kind}' is not allowed for this step. Allowed kinds: {request.allowed_step_kinds}")
21
- undeclared = binding_name_to_value.keys() - set(request.output_binding_names)
22
+ if not isinstance(binding_name_to_value, UnsetType) and (
23
+ not isinstance(binding_name_to_value, Mapping) or any(not isinstance(name, str) for name in binding_name_to_value)
24
+ ):
25
+ raise TypeError("binding_name_to_value must be a string-keyed mapping or UNSET")
26
+ supplied_name_to_value = {} if isinstance(binding_name_to_value, UnsetType) else dict(binding_name_to_value)
27
+ undeclared = supplied_name_to_value.keys() - set(request.output_binding_names)
22
28
  if undeclared:
23
29
  raise ExecutionError(f"Undeclared output binding names: {sorted(undeclared)}")
30
+ if isinstance(outcome, RaiseStepOutcome) and supplied_name_to_value:
31
+ raise ExecutionError("Raise cannot include output bindings")
24
32
  resolved: StepResult
25
33
  if isinstance(outcome, ReturnStepOutcome):
26
34
  value = await evaluate_expression_operation(
@@ -29,7 +37,7 @@ async def resolve_model_candidate(
29
37
  purpose="return",
30
38
  allow_awaitable_result=request.allow_awaitable_return,
31
39
  expected_type=request.return_annotation,
32
- override_name_to_value=binding_name_to_value,
40
+ override_name_to_value=supplied_name_to_value,
33
41
  )
34
42
  resolved = Return(value)
35
43
  elif isinstance(outcome, RaiseStepOutcome):
@@ -46,4 +54,4 @@ async def resolve_model_candidate(
46
54
  resolved = Raise(outcome.raise_message, error_type)
47
55
  else:
48
56
  resolved = {"pass": Pass, "break": Break, "continue": Continue}[outcome.kind]()
49
- return StepCandidate(outcome=resolved, binding_name_to_value=binding_name_to_value)
57
+ return StepCandidate(outcome=resolved, binding_name_to_value=UNSET if isinstance(binding_name_to_value, UnsetType) else supplied_name_to_value)
@@ -13,7 +13,7 @@ from .bindings import MISSING_ANNOTATION, infer_binding_types, normalize_binding
13
13
  from .description import discover_type_alias_reference_names
14
14
  from .environment import StepEnvironment
15
15
  from .scoping import _current_task_references, get_execution_reference
16
- from .step_context import StepContext, ToolResultRenderingPolicy, get_python_cell_scope_stack, get_step_context_stack
16
+ from .step_context import StepContext, ToolResultRenderingPolicy, get_python_cell_scope_stack
17
17
  from .step_contract import StepKind
18
18
  from .step_executor import StepExecutor
19
19
 
@@ -85,8 +85,6 @@ class StepPreparation:
85
85
  python_locals = caller_frame.f_locals
86
86
  python_globals = caller_frame.f_globals
87
87
  step_locals = {name: value for name, value in python_locals.items() if name not in self.runtime_name_set}
88
- parent_stack = get_step_context_stack()
89
- parent_name_to_value = parent_stack[-1].environment.snapshot_working_state() if parent_stack else {}
90
88
  cell_scope_stack = get_python_cell_scope_stack()
91
89
  python_name_to_cell = cell_scope_stack[-1] if cell_scope_stack else {}
92
90
 
@@ -119,7 +117,6 @@ class StepPreparation:
119
117
  python_globals=python_globals,
120
118
  local_name_set=frozenset(local_name_set),
121
119
  python_name_to_cell=python_name_to_cell,
122
- parent_name_to_value=parent_name_to_value,
123
120
  task_name_to_value=scoped_task_name_to_value,
124
121
  output_name_set=frozenset(output_binding_names),
125
122
  binding_name_to_annotation=binding_name_to_annotation,
@@ -452,12 +452,7 @@ def _collect_explicit_multimodal_reference_and_value_list(
452
452
  continue
453
453
 
454
454
  root_reference = reference_path.split(".", 1)[0]
455
- local_names = (
456
- step_context.step_locals.keys()
457
- | step_context.environment.parent_name_to_value.keys()
458
- | step_context.environment.step_name_to_value.keys()
459
- | step_context.binding_commit_targets
460
- )
455
+ local_names = step_context.step_locals.keys() | step_context.environment.step_name_to_value.keys() | step_context.binding_commit_targets
461
456
  if (root_reference in local_names) != include_locals:
462
457
  continue
463
458
 
@@ -553,7 +548,6 @@ def _render_globals_section(
553
548
  for reference in sorted(references):
554
549
  if (
555
550
  reference in step_context.step_locals
556
- or reference in step_context.environment.parent_name_to_value
557
551
  or reference in step_context.environment.step_name_to_value
558
552
  or reference in step_context.binding_commit_targets
559
553
  ):
@@ -695,10 +689,7 @@ def _build_user_prompt_content(
695
689
 
696
690
  locals_content = _render_locals_section(
697
691
  step_context=step_context,
698
- references=step_context.step_locals.keys()
699
- | step_context.environment.parent_name_to_value.keys()
700
- | step_context.environment.step_name_to_value.keys()
701
- | step_context.binding_commit_targets,
692
+ references=step_context.step_locals.keys() | step_context.environment.step_name_to_value.keys() | step_context.binding_commit_targets,
702
693
  explicit_reference_paths=explicit_reference_paths,
703
694
  render_context=render_context,
704
695
  )
@@ -728,12 +719,7 @@ def _build_user_prompt_content(
728
719
  "Displayed annotations are best-effort metadata and may be unresolved source spelling; they are not evaluated for presentation.",
729
720
  ]
730
721
  relevant_names = (
731
- set(references)
732
- | step_context.binding_commit_targets
733
- | set(step_context.step_locals)
734
- | set(environment.parent_name_to_value)
735
- | set(environment.task_name_to_value)
736
- | {"inspect"}
722
+ set(references) | step_context.binding_commit_targets | set(step_context.step_locals) | set(environment.task_name_to_value) | {"inspect"}
737
723
  )
738
724
  collision_names = [
739
725
  name
@@ -24,7 +24,6 @@ from ..oversight import (
24
24
  )
25
25
  from .async_bridge import run_coroutine_synchronously
26
26
  from .bindings import BindingValidationError, require_declared_binding_names, validate_output_bindings
27
- from .environment import UnboundEnvironmentNameError
28
27
  from .execution_reference import ExecutionReference
29
28
  from .interruption import chain_cleanup_errors
30
29
  from .notifications import await_notification
@@ -45,7 +44,7 @@ from .step_context import (
45
44
  )
46
45
  from .step_contract import StepKind
47
46
  from .step_executor import AsyncStepExecutor, StepExecutor, SyncStepExecutor
48
- from .step_request import StepCandidate, StepRequest
47
+ from .step_request import StepCandidate, StepRequest, _snapshot_output_bindings
49
48
  from .step_result import Break, Continue, Pass, Raise, Return, StepResult
50
49
 
51
50
 
@@ -301,22 +300,19 @@ class Runner:
301
300
  *,
302
301
  preparation: PreparedStep,
303
302
  step_outcome: StepResult,
304
- bindings: dict[str, object],
303
+ bindings: Mapping[str, object] | UnsetType,
305
304
  ) -> dict[str, object]:
306
- require_declared_binding_names(bindings, preparation.output_binding_names)
305
+ if not isinstance(bindings, UnsetType):
306
+ require_declared_binding_names(bindings, preparation.output_binding_names)
307
307
  if step_outcome.kind == "raise":
308
+ if not isinstance(bindings, UnsetType) and len(bindings):
309
+ raise NighthawkError("StepCandidate Raise cannot include output bindings")
308
310
  return {}
309
- environment = preparation.step_context.environment
310
- selected_binding_name_to_value: dict[str, object] = {}
311
- for name in preparation.output_binding_names:
312
- if name in environment.step_name_to_value:
313
- selected_binding_name_to_value[name] = environment.step_name_to_value[name]
314
- continue
315
- try:
316
- selected_binding_name_to_value[name] = environment.python_name_to_value[name]
317
- except (KeyError, UnboundEnvironmentNameError):
318
- continue
319
- selected_binding_name_to_value.update(bindings)
311
+ selected_binding_name_to_value = (
312
+ _snapshot_output_bindings(preparation.step_context.environment, preparation.output_binding_names)
313
+ if isinstance(bindings, UnsetType)
314
+ else dict(bindings)
315
+ )
320
316
  return self._validate_and_coerce_output_bindings(preparation=preparation, bindings=selected_binding_name_to_value)
321
317
 
322
318
  async def _apply_step_oversight_if_needed(
@@ -403,14 +399,13 @@ class Runner:
403
399
  if not isinstance(candidate, StepCandidate):
404
400
  raise ExecutionError("Step executor must return StepCandidate")
405
401
  step_outcome = candidate.outcome
406
- bindings = dict(candidate.binding_name_to_value)
407
402
  self.attempted_outcome = step_outcome
408
403
  self._require_allowed_step_kind(step_outcome=step_outcome, allowed_step_kinds=preparation.allowed_step_kinds)
409
404
  self.failure_stage = "binding_validation"
410
405
  validated_bindings = self._validate_bindings_for_outcome(
411
406
  preparation=preparation,
412
407
  step_outcome=step_outcome,
413
- bindings=bindings,
408
+ bindings=candidate.binding_name_to_value,
414
409
  )
415
410
  self.validated_binding_name_to_value = validated_bindings
416
411
  outcome = step_outcome
@@ -334,7 +334,7 @@ def _validate_step_executor(step_executor: object) -> None:
334
334
  def run(
335
335
  step_executor: StepExecutor,
336
336
  *,
337
- run_id: str | None = None,
337
+ run_id: str | UnsetType = UNSET,
338
338
  usage_meter: UsageMeter | UnsetType = UNSET,
339
339
  ) -> Iterator[None]:
340
340
  """Start an execution run with the given step executor.
@@ -344,8 +344,8 @@ def run(
344
344
 
345
345
  Args:
346
346
  step_executor: The step executor to use for Natural block execution.
347
- run_id: Optional identifier for the run. If not provided, a ULID is
348
- generated automatically.
347
+ run_id: Nonempty identifier for the run. Omission or UNSET generates
348
+ a fresh ULID. None and empty strings are invalid.
349
349
  usage_meter: Optional meter that accumulates LLM usage for the run.
350
350
  If not provided, a fresh :class:`UsageMeter` is created.
351
351
 
@@ -356,8 +356,12 @@ def run(
356
356
  TypeError: If the executor does not implement either execution protocol or the usage meter is invalid.
357
357
  """
358
358
  _validate_step_executor(step_executor)
359
+ if not isinstance(run_id, (str, UnsetType)):
360
+ raise TypeError("run_id must be a nonempty string or UNSET")
361
+ if isinstance(run_id, str) and not run_id:
362
+ raise ValueError("run_id must not be empty")
359
363
  execution_reference = ExecutionReference(
360
- run_id=run_id or generate_ulid(),
364
+ run_id=generate_ulid() if isinstance(run_id, UnsetType) else run_id,
361
365
  scope_id=generate_ulid(),
362
366
  step_execution_id=None,
363
367
  source_location=None,
@@ -434,6 +438,7 @@ def scope(
434
438
  installing context. Executor replacement precedes delegated model selection.
435
439
  model replaces only the model; step_executor_configuration replaces all configuration
436
440
  fields. Supplying both is invalid. Selection preserves unrelated scoped controls.
441
+ A new scope retains the active step identity and its source location.
437
442
  """
438
443
  from ..lifecycle import StepLifecycle
439
444
  from ..oversight import Oversight
@@ -455,9 +460,7 @@ def scope(
455
460
  raise TypeError("lifecycle must be StepLifecycle, None, or UNSET")
456
461
  next_step_executor = current_step_executor if isinstance(step_executor, UnsetType) else step_executor
457
462
  selection = _model_selection(model, step_executor_configuration)
458
- if selection is not None:
459
- next_step_executor = _resolve_model_selection(next_step_executor, selection)
460
- next_execution_reference = replace(current_execution_reference, scope_id=generate_ulid(), step_execution_id=None, source_location=None)
463
+ next_execution_reference = replace(current_execution_reference, scope_id=generate_ulid())
461
464
  next_usage_meter = get_usage_meter() if isinstance(usage_meter, UnsetType) else usage_meter
462
465
  next_oversight = _oversight_var.get() if isinstance(oversight, UnsetType) else oversight
463
466
  next_system_prompt_suffix_fragments = _compose_sequence(_system_prompt_suffix_fragments_var.get(), system_prompt_suffix_fragments)
@@ -481,6 +484,9 @@ def scope(
481
484
  entries = _compose_sequence((), tools)
482
485
  next_tools = resolve_scoped_tools((), entries)
483
486
 
487
+ if selection is not None:
488
+ next_step_executor = _resolve_model_selection(next_step_executor, selection)
489
+
484
490
  step_executor_token = _step_executor_var.set(next_step_executor)
485
491
  execution_reference_token = _execution_reference_var.set(next_execution_reference)
486
492
  usage_meter_token = _usage_meter_var.set(next_usage_meter)
@@ -46,7 +46,8 @@ class StepContext:
46
46
 
47
47
  ``environment`` owns effective lookup and tracked step assignments.
48
48
  Use :meth:`record_assignment` or the common assignment operations to keep that environment and ``step_locals`` bookkeeping consistent.
49
- Revision and mutation marks describe tracked changes; finalization independently validates all initialized declared outputs, with explicit candidate proposals taking precedence.
49
+ Revision and mutation marks describe tracked changes.
50
+ Finalization validates initialized declared outputs when the candidate omits its output selection; an explicit mapping supplies the complete selection.
50
51
  """
51
52
 
52
53
  execution_reference: ExecutionReference
@@ -139,10 +140,6 @@ def python_cell_scope(name_to_cell: dict[str, CellType]) -> Iterator[None]:
139
140
  _python_lookup_state_var.reset(token)
140
141
 
141
142
 
142
- def get_step_context_stack() -> tuple[StepContext, ...]:
143
- return _step_context_stack_var.get()
144
-
145
-
146
143
  def get_python_cell_scope_stack() -> tuple[dict[str, CellType], ...]:
147
144
  return _python_lookup_state_var.get().python_cell_scope_stack
148
145
 
@@ -7,8 +7,10 @@ from pydantic_ai import Agent, RunContext, StructuredDict
7
7
  from pydantic_ai.capabilities import AbstractCapability
8
8
  from pydantic_ai.messages import UserContent
9
9
  from pydantic_ai.models import ModelRequestContext
10
+ from pydantic_ai.settings import ModelSettings
10
11
  from pydantic_ai.toolsets.function import FunctionToolset
11
12
 
13
+ from ..composition import UNSET, UnsetType
12
14
  from ..configuration import ModelSelection, StepExecutorConfiguration
13
15
  from ..errors import ExecutionError, NighthawkError
14
16
  from ..tools._admission import ToolAdmissionCapability
@@ -125,10 +127,6 @@ def _new_agent_step_executor(
125
127
  case _:
126
128
  model = model_identifier
127
129
 
128
- constructor_arguments: dict[str, Any] = {}
129
- if configuration.model_settings is not None:
130
- constructor_arguments["model_settings"] = configuration.model_settings
131
-
132
130
  system_prompt_text = build_system_prompt(configuration=configuration)
133
131
 
134
132
  agent = Agent(
@@ -136,7 +134,7 @@ def _new_agent_step_executor(
136
134
  output_type=StepFinalResult,
137
135
  deps_type=StepContext,
138
136
  system_prompt=system_prompt_text,
139
- **constructor_arguments,
137
+ model_settings=cast(ModelSettings, configuration.model_settings),
140
138
  )
141
139
 
142
140
  @agent.system_prompt(dynamic=True)
@@ -173,10 +171,14 @@ class AgentStepExecutor:
173
171
 
174
172
  def __init__(
175
173
  self,
176
- configuration: StepExecutorConfiguration | None = None,
174
+ configuration: StepExecutorConfiguration | UnsetType = UNSET,
177
175
  agent: StepExecutionAgent | None = None,
178
176
  ) -> None:
179
- self.configuration = configuration or StepExecutorConfiguration()
177
+ if not isinstance(configuration, (StepExecutorConfiguration, UnsetType)):
178
+ raise TypeError("configuration must be StepExecutorConfiguration or UNSET")
179
+ if agent is not None and not isinstance(agent, (AsyncExecutionAgent, SyncExecutionAgent)):
180
+ raise TypeError("agent must implement the synchronous or asynchronous agent protocol")
181
+ self.configuration = StepExecutorConfiguration() if isinstance(configuration, UnsetType) else configuration
180
182
  if agent is not None and not isinstance(self.configuration.model, str):
181
183
  raise NighthawkError("The external agent owns model selection; configuration.model must be a string")
182
184
  self.agent_is_managed = agent is None
@@ -193,15 +195,17 @@ class AgentStepExecutor:
193
195
  cls,
194
196
  *,
195
197
  agent: StepExecutionAgent,
196
- configuration: StepExecutorConfiguration | None = None,
198
+ configuration: StepExecutorConfiguration | UnsetType = UNSET,
197
199
  ) -> AgentStepExecutor:
198
200
  """Create an executor wrapping an existing agent.
199
201
 
200
202
  Args:
201
203
  agent: A pre-configured agent to use for step execution.
202
- configuration: Optional configuration. Defaults to
203
- StepExecutorConfiguration().
204
+ configuration: Omission or UNSET creates StepExecutorConfiguration().
205
+ None and raw dictionaries are invalid; parse configuration data explicitly.
204
206
  """
207
+ if not isinstance(agent, (AsyncExecutionAgent, SyncExecutionAgent)):
208
+ raise TypeError("agent must implement the synchronous or asynchronous agent protocol")
205
209
  return cls(configuration=configuration, agent=agent)
206
210
 
207
211
  @classmethod
@@ -211,6 +215,8 @@ class AgentStepExecutor:
211
215
  configuration: StepExecutorConfiguration,
212
216
  ) -> AgentStepExecutor:
213
217
  """Create an executor from a configuration, building a managed agent internally."""
218
+ if not isinstance(configuration, StepExecutorConfiguration):
219
+ raise TypeError("configuration must be StepExecutorConfiguration")
214
220
  return cls(configuration=configuration)
215
221
 
216
222
  def resolve_model_selection(self, selection: ModelSelection) -> StepExecutor:
@@ -374,7 +380,7 @@ class AgentStepExecutor:
374
380
  usage_meter.record(result.usage, kind="step")
375
381
 
376
382
  step_outcome = self._parse_agent_result(result)
377
- return await resolve_model_candidate(request, step_outcome, {})
383
+ return await resolve_model_candidate(request, step_outcome)
378
384
 
379
385
  def run_step(
380
386
  self,
@@ -1,14 +1,29 @@
1
1
  """Frozen executor contracts containing live application values."""
2
2
 
3
- from collections.abc import Mapping
3
+ from collections.abc import Mapping, Sequence
4
4
  from types import MappingProxyType
5
5
 
6
- from pydantic import BaseModel, ConfigDict, Field, InstanceOf, field_validator, model_validator
6
+ from pydantic import BaseModel, ConfigDict, InstanceOf, field_validator, model_validator
7
7
 
8
+ from ..composition import UNSET, UnsetType
9
+ from .environment import StepEnvironment, UnboundEnvironmentNameError
8
10
  from .execution_reference import ExecutionReference
9
11
  from .step_context import StepContext
10
12
  from .step_contract import StepKind
11
- from .step_result import StepResult
13
+ from .step_result import Raise, StepResult
14
+
15
+
16
+ def _snapshot_output_bindings(environment: StepEnvironment, output_binding_names: Sequence[str]) -> dict[str, object]:
17
+ binding_name_to_value: dict[str, object] = {}
18
+ for name in output_binding_names:
19
+ if name in environment.step_name_to_value:
20
+ binding_name_to_value[name] = environment.step_name_to_value[name]
21
+ continue
22
+ try:
23
+ binding_name_to_value[name] = environment.python_name_to_value[name]
24
+ except (KeyError, UnboundEnvironmentNameError):
25
+ continue
26
+ return binding_name_to_value
12
27
 
13
28
 
14
29
  class StepRequest(BaseModel):
@@ -29,6 +44,17 @@ class StepRequest(BaseModel):
29
44
  allowed_step_kinds: tuple[StepKind, ...]
30
45
  allow_awaitable_return: bool = False
31
46
 
47
+ def snapshot_output_bindings(self) -> dict[str, object]:
48
+ """Snapshot initialized declared outputs in declaration order.
49
+
50
+ Tracked assignments precede current Python values, including live globals and cells.
51
+ Unbound names and task-only references are excluded.
52
+ The dictionary is independent; values retain identity.
53
+ This neither validates nor writes values.
54
+ Later working changes do not change its name associations.
55
+ """
56
+ return _snapshot_output_bindings(self.step_context.environment, self.output_binding_names)
57
+
32
58
  @field_validator("binding_name_to_type", mode="after")
33
59
  @classmethod
34
60
  def freeze_contracts(cls, value: Mapping[str, object]) -> Mapping[str, object]:
@@ -48,14 +74,26 @@ class StepRequest(BaseModel):
48
74
 
49
75
 
50
76
  class StepCandidate(BaseModel):
51
- """Resolved control flow and proposed writes, pending runtime finalization."""
77
+ """Resolved control flow and output selection, pending runtime validation.
78
+
79
+ Omission or UNSET selects initialized declared outputs at finalization.
80
+ A mapping supplies the entire output selection; {} selects no writes.
81
+ Explicit selections receive no supplementation from the working environment.
82
+ Raise permits only omitted or empty outputs.
83
+ """
52
84
 
53
85
  model_config = ConfigDict(frozen=True, arbitrary_types_allowed=True, extra="forbid", validate_default=True)
54
86
 
55
87
  outcome: StepResult
56
- binding_name_to_value: Mapping[str, object] = Field(default_factory=dict)
88
+ binding_name_to_value: Mapping[str, object] | UnsetType = UNSET
57
89
 
58
90
  @field_validator("binding_name_to_value", mode="after")
59
91
  @classmethod
60
- def preserve_live_values(cls, value: Mapping[str, object]) -> Mapping[str, object]:
61
- return MappingProxyType(dict(value))
92
+ def preserve_live_values(cls, value: Mapping[str, object] | UnsetType) -> Mapping[str, object] | UnsetType:
93
+ return value if isinstance(value, UnsetType) else MappingProxyType(dict(value))
94
+
95
+ @model_validator(mode="after")
96
+ def validate_output_selection(self) -> "StepCandidate":
97
+ if isinstance(self.outcome, Raise) and not isinstance(self.binding_name_to_value, UnsetType) and len(self.binding_name_to_value):
98
+ raise ValueError("StepCandidate Raise cannot include output bindings")
99
+ return self
nighthawk/testing.py CHANGED
@@ -6,9 +6,12 @@ deterministic tests of Natural functions without LLM API calls.
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
- from collections.abc import Callable
10
- from dataclasses import dataclass, field
9
+ from collections.abc import Callable, Mapping
10
+ from dataclasses import dataclass
11
11
 
12
+ from pydantic import Field
13
+
14
+ from .composition import UNSET, UnsetType
12
15
  from .runtime.execution_reference import ExecutionReference
13
16
  from .runtime.step_context import StepContext
14
17
  from .runtime.step_contract import StepKind
@@ -42,20 +45,19 @@ class StepCall:
42
45
  step_locals: dict[str, object]
43
46
  step_globals: dict[str, object]
44
47
  task_name_to_value: dict[str, object]
45
- parent_name_to_value: dict[str, object]
46
48
 
47
49
 
48
- @dataclass
49
- class StepResponse:
50
+ class StepResponse(StepCandidate):
50
51
  """Scripted response for a single Natural block execution.
51
52
 
52
53
  Attributes:
53
- bindings: Proposed output values. Undeclared names fail finalization.
54
+ binding_name_to_value: Complete output selection.
55
+ Omission or UNSET selects initialized declared outputs; {} selects no writes.
56
+ Undeclared names fail finalization.
54
57
  outcome: Resolved Python control result. Strings are values, never expressions.
55
58
  """
56
59
 
57
- bindings: dict[str, object] = field(default_factory=dict)
58
- outcome: StepResult = field(default_factory=Pass)
60
+ outcome: StepResult = Field(default_factory=Pass)
59
61
 
60
62
 
61
63
  def _build_step_call(
@@ -67,7 +69,7 @@ def _build_step_call(
67
69
  referenced_global_names = (
68
70
  step_context.read_binding_names | (set(step_context.environment.task_name_to_value) | step_context.type_alias_reference_names)
69
71
  ) - step_context.step_locals.keys()
70
- filtered_globals = {name: step_context.step_globals[name] for name in referenced_global_names if name in step_context.step_globals}
72
+ global_name_to_value = {name: step_context.step_globals[name] for name in referenced_global_names if name in step_context.step_globals}
71
73
  return StepCall(
72
74
  execution_reference=step_context.execution_reference,
73
75
  natural_program=processed_natural_program,
@@ -75,9 +77,8 @@ def _build_step_call(
75
77
  binding_name_to_type=dict(step_context.binding_name_to_type),
76
78
  allowed_step_kinds=allowed_step_kinds,
77
79
  step_locals=dict(step_context.step_locals),
78
- step_globals=filtered_globals,
80
+ step_globals=global_name_to_value,
79
81
  task_name_to_value=dict(step_context.environment.task_name_to_value),
80
- parent_name_to_value=dict(step_context.environment.parent_name_to_value),
81
82
  )
82
83
 
83
84
 
@@ -117,7 +118,7 @@ class ScriptedExecutor:
117
118
  f"{len(self.responses)} responses were supplied. "
118
119
  "Supply another response or an explicit default_response."
119
120
  )
120
- return StepCandidate(outcome=response.outcome, binding_name_to_value=response.bindings)
121
+ return response
121
122
 
122
123
  async def run_step_async(
123
124
  self,
@@ -148,7 +149,7 @@ class CallbackExecutor:
148
149
  )
149
150
  self.calls.append(call)
150
151
  response = self.handler(call)
151
- return StepCandidate(outcome=response.outcome, binding_name_to_value=response.bindings)
152
+ return response
152
153
 
153
154
  async def run_step_async(
154
155
  self,
@@ -158,9 +159,9 @@ class CallbackExecutor:
158
159
  return self.run_step(request=request)
159
160
 
160
161
 
161
- def pass_response(**bindings: object) -> StepResponse:
162
- """Create a response with pass outcome and optional binding values."""
163
- return StepResponse(bindings=bindings)
162
+ def pass_response(*, binding_name_to_value: Mapping[str, object] | UnsetType = UNSET) -> StepResponse:
163
+ """Continue with automatic output selection, or an explicit complete mapping."""
164
+ return StepResponse(binding_name_to_value=binding_name_to_value)
164
165
 
165
166
 
166
167
  def raise_response(message: str, *, error_type: type[BaseException] = Exception) -> StepResponse:
@@ -176,21 +177,22 @@ def raise_response(message: str, *, error_type: type[BaseException] = Exception)
176
177
  return StepResponse(outcome=Raise(message, error_type))
177
178
 
178
179
 
179
- def return_response(value: object, /, **bindings: object) -> StepResponse:
180
+ def return_response(value: object, /, *, binding_name_to_value: Mapping[str, object] | UnsetType = UNSET) -> StepResponse:
180
181
  """Return a Python value subject to the Natural function's return contract.
181
182
 
182
183
  Args:
183
184
  value: Resolved value; strings are returned literally without evaluation.
184
- **bindings: Proposed output values, independently validated at finalization.
185
+ binding_name_to_value: Complete output selection; omission or UNSET selects initialized declared outputs.
186
+ {} selects no writes.
185
187
  """
186
- return StepResponse(bindings=bindings, outcome=Return(value))
188
+ return StepResponse(binding_name_to_value=binding_name_to_value, outcome=Return(value))
187
189
 
188
190
 
189
- def break_response() -> StepResponse:
191
+ def break_response(*, binding_name_to_value: Mapping[str, object] | UnsetType = UNSET) -> StepResponse:
190
192
  """Create a response with break outcome (exit enclosing loop)."""
191
- return StepResponse(outcome=Break())
193
+ return StepResponse(outcome=Break(), binding_name_to_value=binding_name_to_value)
192
194
 
193
195
 
194
- def continue_response() -> StepResponse:
196
+ def continue_response(*, binding_name_to_value: Mapping[str, object] | UnsetType = UNSET) -> StepResponse:
195
197
  """Create a response with continue outcome (skip to next iteration)."""
196
- return StepResponse(outcome=Continue())
198
+ return StepResponse(outcome=Continue(), binding_name_to_value=binding_name_to_value)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: nighthawk-python
3
- Version: 0.15.0
3
+ Version: 0.16.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/
@@ -1,13 +1,13 @@
1
1
  nighthawk/__init__.py,sha256=A_3q-S4wUnuy_GzRlI8TSADfy2cQNPfOdh2y6eIvdHg,2390
2
- nighthawk/composition.py,sha256=6ffFoFilnaDk17pRlU-pm330KnRIoDb5BNHEmvXVxj0,1914
3
- nighthawk/configuration.py,sha256=84MDKupaNaEiOTeoABjvRbXsC-UjWQR7JrAW4mziA9M,9024
2
+ nighthawk/composition.py,sha256=qd5NgA3p4RiFeTgZEnSruEcVmAejolP3xAgT226m3OA,2070
3
+ nighthawk/configuration.py,sha256=3w4fAihCGub9ubSwlwAtVTIHSLPU4wUu-_ZRpdcbf4w,9127
4
4
  nighthawk/errors.py,sha256=U9ogl_0MLA1xXe_bU2RCA1gRWN0ev5E4a3FiBLgpilI,1282
5
- nighthawk/function_call.py,sha256=eHWL0wpXcosRQMIl8GmOcPHJXc4n40PvGbXYmdKLlJw,6138
5
+ nighthawk/function_call.py,sha256=o_i86cbsRxC0kMsTXCWvijA7SJmC7oNrcEghTNvpWJE,6745
6
6
  nighthawk/identifier_path.py,sha256=QRbDte7j0mubwg8ebs1GlxW7QeA5V_ow29MpVPCg6Ck,626
7
7
  nighthawk/json_renderer.py,sha256=QT4-kyKcr_rcga5hg2irKZ4a2OH6XJ8c0_b1zBlPjcE,10501
8
8
  nighthawk/lifecycle.py,sha256=pHCTZCMbG7iTVmLOWuwspi4DeM5p2Y5BskONFF_beTY,5513
9
- nighthawk/oversight.py,sha256=STOFtR8hx5wIVPiTUPjc0FsNEMUp_DSYsy_6o994LoE,5382
10
- nighthawk/testing.py,sha256=u4phjHPzgoAmbSHsi1vbG6k30BWOa4hCIa-TKYI7yAs,7732
9
+ nighthawk/oversight.py,sha256=xCuJ5kARG0P2KS0eaf4pHqvkUYVgSULNqB-QLXEKxGU,5762
10
+ nighthawk/testing.py,sha256=yJE2ru0igXknjN2CCpLur2XGSCGI-ww_58Y8XK4h4iw,8045
11
11
  nighthawk/ulid.py,sha256=CBpGLXDGm2j69Yk15hEq4Z5V8StWOY4714ow4BZn5tc,546
12
12
  nighthawk/backends/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  nighthawk/backends/_text_fallback.py,sha256=h4dt3nU73Uwd6rxn2zLx7trnlxgxKVhNYWc3xZjTMeg,3606
@@ -45,24 +45,24 @@ nighthawk/runtime/context.py,sha256=fJlrV8-Ty0P5dATYQsW4aPCGErH90JJYly_H2mqZyIM,
45
45
  nighthawk/runtime/description.py,sha256=tLL_tD6RTUFGBBz64QZo9atwHaWDLd0SpQPnZ2JkUc4,21411
46
46
  nighthawk/runtime/discovery.py,sha256=Z0gRU0xVQfxfZ-2ffol3CKwIWg5vWDchOU59ucN9Lpc,5145
47
47
  nighthawk/runtime/embedded.py,sha256=59wZE7ooLpnxxRvS1YwqQzcIqOo9DBoWJD1PlFRxWbE,4961
48
- nighthawk/runtime/environment.py,sha256=0Jyw2PM9MrZDN--p5V5fNyb5ni0v-hcT0vrlAqiIOIg,12843
48
+ nighthawk/runtime/environment.py,sha256=hvmAS9N2_cbxxzQcJ4QZJfkhufPrrw1gcJY0ct7rsx8,14038
49
49
  nighthawk/runtime/execution_reference.py,sha256=9sMqcpS8kmyNWEPruFnuYMaOqgpDszcfk2Rofqr2Eek,410
50
- nighthawk/runtime/expression.py,sha256=xYIGGuO4MEJQ8KKdz7IAlC9WJ1mUfmAjCwYJYtCQi54,4000
50
+ nighthawk/runtime/expression.py,sha256=421HuL53GGEKW1Tuag9VRRcJffmPShpJ0rTRmZYkxWM,4042
51
51
  nighthawk/runtime/inspection.py,sha256=06zWKw_u61585qc-Wp1QmvKAekydWoCcmaDEU22Hdbw,3454
52
52
  nighthawk/runtime/interruption.py,sha256=9B3hjMbXXtjf1sP2qVAf10dWk0KDS_UttZwqaix1tFU,679
53
- nighthawk/runtime/model_candidate.py,sha256=cfRFzNkX9ogln5GUFoqf3GYYq6hShelc1mEHKqrMiBQ,2461
53
+ nighthawk/runtime/model_candidate.py,sha256=X0aJvlk9zSDTwU6KCnYLBnOuiKab-E4IqVEeypvljhs,3122
54
54
  nighthawk/runtime/notifications.py,sha256=khLidCGr9lc2aed_GwGiWaUOx6jNQepGqY0_a6flJWo,1449
55
55
  nighthawk/runtime/operation_contract.py,sha256=zTZTQXrv6xpUgJxUIGB3xVFDQZ0NglgmdU30aFbOf9Q,2471
56
56
  nighthawk/runtime/operations.py,sha256=OQ15NKnSs2NSUgRxFALzS6bRCWPgbzNrjXCOOW6GH7A,6706
57
- nighthawk/runtime/preparation.py,sha256=Bm80_F6zKrBaMGaLlTOyfHK7qEBAMN8sUMRU4m3XVWE,11632
58
- nighthawk/runtime/prompt.py,sha256=5SoDBHLNC6hRqJX3IDRhk1HJO4raoFzsaATIOQCLfK8,30098
59
- nighthawk/runtime/runner.py,sha256=rMf0ZzD4Xp8LGs3weWGwEiwIxlr-h5Ekg4NDVwY6VJ8,27287
60
- nighthawk/runtime/scoping.py,sha256=hjoauK3PCTFKTeIWDyjIN2SqoBOrmwAHcSk_vBYmFKc,21309
61
- nighthawk/runtime/step_context.py,sha256=7n4ZIwdCIVaD1n-klRDcalPpoxCAtS2wTUCLnYQ8qd0,5904
57
+ nighthawk/runtime/preparation.py,sha256=zLrnP53k7PrKv4gTYf_RJDB2dOEbI_KxB7JSqPzG0EM,11396
58
+ nighthawk/runtime/prompt.py,sha256=ihKOoMEWiTKX6SsyXiAvXydPfgnIAQBrBhDsBib2_ak,29750
59
+ nighthawk/runtime/runner.py,sha256=I5A2CnqzxcWAmIQlGO46h5p460RB_ibHxnLktDj0g7c,27097
60
+ nighthawk/runtime/scoping.py,sha256=94l8lW9UWDSAh4e1-lG1GOt3l7vlm_KMfAcCTbGGCM0,21623
61
+ nighthawk/runtime/step_context.py,sha256=DM7mHCiiFhulUAqS6qK7TAZLHfOe-ZqRtZHkEwj22OE,5836
62
62
  nighthawk/runtime/step_contract.py,sha256=xIQcpD1PDY_gqrTqSUZ_5yZoMQt1EJOU33fpn-Da_28,6737
63
63
  nighthawk/runtime/step_definition.py,sha256=f64IVSAfpbxUpFVdIKBuWW43m2sQRQDvVmFgBrQeKbE,1913
64
- nighthawk/runtime/step_executor.py,sha256=4YSkZctDalR8OyXXKEp0TeME-Zs5UucMAPd0qQ9O_2U,15326
65
- nighthawk/runtime/step_request.py,sha256=04z-TYR5b-_7yQoeAwqg9YGFNGaglbstFFfzLn-mgzw,2563
64
+ nighthawk/runtime/step_executor.py,sha256=Q7Teu6mUrForLi1q6Q570ZRkP44KZ-9KoodUUXnJEXs,16089
65
+ nighthawk/runtime/step_request.py,sha256=qs_kJZ_SqvYSD4yTs-5jSoHVrTbVT27YxWUFHeLHLjM,4508
66
66
  nighthawk/runtime/step_result.py,sha256=mCdteQgQMMwoYTvhCJLuwcctljw-rjjyoFy2awfiRRg,1678
67
67
  nighthawk/runtime/tool_calls.py,sha256=IZQWk2IH8rPF6hjVBzBDn0cpGDkG9k368dDFpmquYOg,3318
68
68
  nighthawk/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -74,7 +74,7 @@ nighthawk/tools/contracts.py,sha256=X-YixPwBun0FmjiyPPNCXPW2pJf7CpmWR7jyTVJQvsM,
74
74
  nighthawk/tools/declarations.py,sha256=goPDT7iVaXez11R_9z8e4czeeA7wevmZPs8sO85hqdA,3986
75
75
  nighthawk/tools/execution.py,sha256=IeZs32tUSLJkj7_M94vrWh0Xt1CkJgvoUike9iIKe1I,6720
76
76
  nighthawk/tools/provided.py,sha256=H17PoX8ztUmtgrBkB-bEdfDkK5OSULIl_tyMgzEc3g4,1958
77
- nighthawk_python-0.15.0.dist-info/METADATA,sha256=QUwsH_cWrn_ywrZYLhSLCGyOlp2JdV-1w2G7xRSh7I4,6147
78
- nighthawk_python-0.15.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
79
- nighthawk_python-0.15.0.dist-info/licenses/LICENSE,sha256=R8tjrX79o5Pbi_l_aFPPWDbTZkmz4is0CxmzMMs3f0A,1076
80
- nighthawk_python-0.15.0.dist-info/RECORD,,
77
+ nighthawk_python-0.16.0.dist-info/METADATA,sha256=g2-niQ9VaMFvMMN3Rn0advG6wBR-Oume9kwYaC_UvjI,6147
78
+ nighthawk_python-0.16.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
79
+ nighthawk_python-0.16.0.dist-info/licenses/LICENSE,sha256=R8tjrX79o5Pbi_l_aFPPWDbTZkmz4is0CxmzMMs3f0A,1076
80
+ nighthawk_python-0.16.0.dist-info/RECORD,,