Agent Parts and Harness¶
Agent Parts are reusable judgments and operations; the Harness supplies host-owned execution mechanisms. AI agents combine Natural judgment with Python computation and actions to construct capabilities, carry out work, assess results and effects, and improve later attempts. Python defines execution structure even when an agent authors that structure during execution.
The library supports multiple execution strategies, including recursive code generation, procedure discovery, explicit learning, and application-defined compositions. No single agent loop or conversation framework is required. Reusable Sessions support embedding, and an optional repository-local terminal demonstrates an interactive application.
Read Quickstart for setup. The Agent principles apply the Nighthawk philosophy to Harness and Agent Parts responsibilities.
Responsibility boundaries¶
Core supplies Natural/Python execution semantics; Harness admits and records governed execution; Parts compose judgments and domain operations. Their packages and dependency direction are defined in Architecture. Parts range from an individual judgment to a complete strategy and combine through ordinary Python calls.
Applications supply goals, domain actions, policy, storage, runtime services, source approval, and capability improvement workflows.
What it provides¶
- Grants: immutable name-to-reference mappings that bound what a Natural execution or generated execution may reach. Capabilities carry a stable name, a typed signature, an authority boundary, and a declared effect footprint.
- Host-owned dispatch: generated Python source enters execution only as a validated
CodeProposalthroughdispatch()or the grantedexecute_codecapability. Materialized definitions get stable identities and are re-entered through host mediation on every call. - A run ledger: an append-only typed record of run entry,
executeanddecideNatural execution outcomes, dispatches and validated invocation inputs, definitions, capability requests, perimeter effects, and run end. - A platform contract: opaque resource allocation and perimeter audit storage are supplied by the host; external confinement and forced branch termination are optional services whose guarantees the library never fabricates.
- An optional fixed-loop composition in
nighthawk.parts.exploration, using the same Harness mechanisms as other strategies. - Domain capabilities in
nighthawk.parts.commands,files,web,images,workspace, andartifacts, composed explicitly into grants.
Compose reusable parts¶
| Need | Public surface |
|---|---|
| Inspect bounded retained values | values.read_text_window, values.search_text, values.select_sequence_window |
| Describe caller-owned procedures | catalog.ProcedureCatalog, catalog.build_procedure_discovery |
| Choose and invoke within current authority | procedures.select_procedure, procedures.selected_procedure |
| Materialize and explicitly retain generated procedures | procedures.materialize_procedure |
| Retain generated functions for fresh runs without a Session | generated.retain_function, generated.RetainedFunction |
| Propose source from explicit requirements and context | generation.FunctionRequest, generation.propose_function |
| Select one conversation step or compose an optional loop | conversation.choose_conversation_step, conversation.converse |
| Execute a selected action or export selected values | conversation.perform_conversation_action, sources.invoke_source, sources.export_source, sources.export_data |
| Inspect causal observations or resolve an exact citation | harness.evidence.analyze_run, parts.evidence.project_evidence, harness.evidence.resolve_evidence |
| Derive explicit lessons and inert candidates | learning.derive_learning, learning.format_candidate_review |
| Admit and account for a Python composition | harness.runner.Runner, harness.run_contract.ScaffoldComposition, harness.run_contract.AsyncScaffoldComposition |
These parts are ordinary functions and values. Applications choose which to combine, where to retain results, and what evidence is sufficient for reuse.
When a Natural block constructs a capability's structured request, expose the request schema explicitly through ordinary Python.
For example, import FilePatchRequest from nighthawk.parts.files and instruct the block to call <FilePatchRequest.model_json_schema> before constructing the patch.
Static callable descriptions are best-effort and do not evaluate Pydantic's deferred constructor signatures; a type name alone does not describe its fields.
The file-patch integration test demonstrates this composition with a writable-root grant and recorded effects.
For example, this body materializes one generated function and invokes it through the same host mechanisms used by other strategies:
from nighthawk.harness.proposals import CodeProposal
from nighthawk.parts.procedures.catalog import ProcedureCatalog
from nighthawk.parts.procedures import materialize_procedure, selected_procedure
from nighthawk.harness.run_contract import RunContract, ScaffoldComposition
def increment(value: int) -> int:
catalog = ProcedureCatalog()
selection = materialize_procedure(
catalog,
CodeProposal(source="def increment(value: int) -> int: return value + 1", export_name_tuple=("increment",)),
export_name="increment",
purpose="Increment an integer",
)
with selected_procedure(catalog, selection) as procedure:
assert callable(procedure)
return procedure(value)
composition = ScaffoldComposition(RunContract(int, int), increment)
# Supply your configured Runner:
# result = runner.run(composition, 6)
This illustrates mediation and composition; ordinary Python already suffices for incrementing a number. The reference applications combine Natural selection, generation, concurrent work, explicit learning, evaluation, and continuation. Their evaluators compare complete results with explicit Python baselines, resolve observed input/result evidence, and distinguish scripted accounting from provider observations.
Reuse generated functions across runs¶
retain_function(proposal, export_name=...) validates and materializes a definition-only proposal in the active run without calling its body.
Its RetainedFunction keeps the exact proposal and observed definition identity.
Applications retain that ordinary Python value wherever they choose; no catalog, Session, storage backend, or approval workflow is required.
Calling it in a later run rematerializes its source under that run's current grant.
Each run shares one materialization per retained callable identity, while every invocation resolves dependencies and passes through normal dispatch admission again.
For an asynchronous function, materialization and invocation begin when its returned coroutine is awaited.
from nighthawk.harness.proposals import CodeProposal
from nighthawk.harness.generated import RetainedFunction, retain_function
from nighthawk.harness.run_contract import RunContract, ScaffoldComposition
proposal = CodeProposal(
source="def increment(value: int) -> int: return value + 1",
export_name_tuple=("increment",),
)
function = runner.run(
ScaffoldComposition(RunContract(type(None), RetainedFunction), lambda value: retain_function(proposal, export_name="increment")),
None,
)
assert runner.run(ScaffoldComposition(RunContract(int, object), function), 6) == 7
assert runner.run(ScaffoldComposition(RunContract(int, object), function), 9) == 10
Retention never approves source or retains the originating run's grant.
Materialized references obtained through function.materialize() still expire with their run.
Ledger materializations in later runs link the selected export to its original retained_definition_id and remain generated provenance.
Private closure state and mutable defaults are run-local; pass persistent state explicitly.
The retained text workflow uses a generation judgment once, then evaluates the same source in multiple fresh batch runs.
Compose conversation judgments independently¶
choose_conversation_step(input, bindings, completed_action, action_result) returns one typed ConversationStep and its selected writes.
It neither runs the selected action nor publishes bindings.
ConversationInput(task, context=...) can carry application-selected values without a Session; its bounded pages are optional integration context.
ConversationBindings supplies the explicit read/write contract and any values retained between judgments.
The optional converse loop combines that judgment with generation.propose_function and perform_conversation_action.
A normal answer needs one judgment.
A request for a new function produces a FunctionRequest; the separate generation judgment returns an inert CodeProposal, and deterministic retention returns the callable to the next conversation step.
Applications can replace choose_step, generate_function, or perform_action with ordinary async functions.
They may also call those parts directly without the loop.
propose_function receives explicit context and dependency descriptions under a narrowed grant with no domain capability tools; generation does not execute or approve its candidate.
sources operations are also independent of conversation and use the current grant for their governed effects.
Session uses these same parts and adds binding publication, callable entry from Python cells, and operator lifecycle.
build_conversation_composition(choose_step=..., generate_function=...) replaces individual judgments while preserving Session integration.
Custom compositions declare trusted Natural helpers in their function_definition_tuple at run entry; this is exact callable identity evidence, not an additional grant or automatic approval.
Optional fixed-loop composition¶
from nighthawk.adapters.pydantic_ai.configuration import StepExecutorConfiguration
from nighthawk.adapters.pydantic_ai.executor import AgentStepExecutor
from nighthawk.harness.grants import Grant
from nighthawk.harness.perimeter import PerimeterPolicy
from nighthawk.harness.run_handle import RunHandle
from nighthawk.harness.dispatch import execute_code_capability
from nighthawk.harness.runner import Runner
from nighthawk.harness.resources import bounded_resource_allocation
from nighthawk.parts.exploration import exploration_composition, ExplorationTask
runner = Runner(
step_executor=AgentStepExecutor(StepExecutorConfiguration(execution_model="openai-responses:gpt-6-luna")),
perimeter_policy=PerimeterPolicy.NONE,
resource_allocation_factory=lambda run_identity: bounded_resource_allocation(
maximum_recursion_depth=3,
maximum_executor_step_count=64,
),
# The default executes in process; optional external confinement must confirm each dispatch.
)
handle = RunHandle()
result = runner.run(
exploration_composition(run_entry_grant=Grant.of(execute_code_capability())),
ExplorationTask(task="Summarize what a run ledger records."),
handle=handle,
)
print(result.summary)
for entry in handle.ledger.entries:
print(entry.sequence, entry.event.kind)
Runtime composition¶
Configure the executor and host services on a Runner, then call runner.run(composition, run_input, run_id=run_id, handle=handle) for an application-defined composition.
The exploration composition and callable examples use the same Runner entry point.
ScaffoldComposition returns its completed result synchronously.
AsyncScaffoldComposition returns an unstarted coroutine from runner.run(...); awaiting it admits the run and returns its completed typed result.
Callers may supply a RunHandle to inspect the ledger or request termination, without creating a Session.
Every run receives a fresh allocation.
The runtime obligations describe the guarantees required from host services, including the audit store that a run permitting perimeter effects requires.
The SQLite audit example demonstrates committed attempt/completion records surviving process restart.
Foreground Sessions¶
nighthawk.session.create_session(execution_model=...) supplies lazy provider configuration for a persistent Python namespace.
Submit a CellInput, then drive it with execution.wait() or await execution.wait_async() on the caller's event loop.
Python-only cells construct no model provider.
Use a SessionManager when the application supplies its own Runner and conversation composition.
Embedding Sessions covers lifecycle, typed writes, source approval, generated functions, history, and model selection, with a runnable Python-only example. The terminal application adds an independent UI process and operator controls.
Development¶
Follow the repository's development and verification commands. Provider acceptance uses the explicit opt-in controls described in the integration test instructions.