Harness
The core ReAct loop implementation. Agent is a convenience layer over
Harness. Use Harness directly when you need explicit control over tool
wiring — see examples/advanced_wiring.py for a complete example.
Harness
data_harness.loop.Harness
Harness(
adapter: ProviderAdapter,
system: str,
tools: list[ToolSpec],
max_turns: int = 25,
run_dir: str = "./runs",
cache: SessionCache | None = None,
)
The core synchronous ReAct loop.
Harness owns the message list, dispatches tools, applies suffix-only
reminder hooks, and logs every turn to a JSONL file. It is the central
implementation boundary in data-harness: everything above it (Agent,
AgentSession) is a convenience layer; everything below it
(ProviderAdapter, SessionCache, ToolSpec) is a pure dependency.
The system prompt is never mutated between turns. Reminders, nags, and dynamic state are always appended to the conversation suffix so the provider's KV cache is not invalidated.
For most use cases, prefer Agent over constructing Harness directly.
Use Harness when you need full control over tool wiring, as shown in
examples/advanced_wiring.py.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
adapter
|
ProviderAdapter
|
Synchronous provider adapter that translates provider SDK objects into harness types. |
required |
system
|
str
|
System prompt. Kept byte-identical across all turns. |
required |
tools
|
list[ToolSpec]
|
Full tool list. Invisible tools ( |
required |
max_turns
|
int
|
Hard cap on provider turns before the loop stops and returns
a |
25
|
run_dir
|
str
|
Directory where JSONL logs are written. Created on first run. |
'./runs'
|
cache
|
SessionCache | None
|
Shared |
None
|
Source code in data_harness/loop.py
run_file
property
Path to the JSONL log for this run, or None before the first run.
register_reminder
Register a suffix reminder hook called before each provider turn.
The hook receives (current_turn, max_turns) and returns a reminder
string to append to the conversation suffix, or None to skip.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
hook
|
Callable[[int, int], str | None]
|
Callable with signature |
required |
Source code in data_harness/loop.py
run_result
run_result(
user_message: str,
*,
run_id: str | None = None,
session_id: str | None = None,
) -> RunResult
Start a fresh run and return the full RunResult.
Resets message history. Use ask_result for follow-up turns on the
same history.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user_message
|
str
|
The initial user prompt. |
required |
run_id
|
str | None
|
Optional identifier stamped into the |
None
|
session_id
|
str | None
|
Optional session identifier stamped into the |
None
|
Returns:
| Type | Description |
|---|---|
RunResult
|
A |
Source code in data_harness/loop.py
ask_result
ask_result(
user_message: str,
*,
run_id: str | None = None,
session_id: str | None = None,
) -> RunResult
Append a follow-up message and continue the existing run.
Appends user_message to the current history without resetting it.
Useful for multi-turn sessions when driving Harness directly.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user_message
|
str
|
The follow-up user prompt. |
required |
run_id
|
str | None
|
Optional identifier stamped into the |
None
|
session_id
|
str | None
|
Optional session identifier stamped into the |
None
|
Returns:
| Type | Description |
|---|---|
RunResult
|
A |
Source code in data_harness/loop.py
run
Start a fresh run and return the final text response.
Raises MaxTurnsExceeded if the loop hits max_turns.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user_message
|
str
|
The initial user prompt. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The model's final text response. |
Raises:
| Type | Description |
|---|---|
MaxTurnsExceeded
|
If the loop reaches |
RuntimeError
|
If the provider raises an exception during the run. |
Source code in data_harness/loop.py
ask
Append a follow-up message and return the final text response.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user_message
|
str
|
The follow-up user prompt. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The model's final text response. |
Raises:
| Type | Description |
|---|---|
MaxTurnsExceeded
|
If the loop reaches |
RuntimeError
|
If the provider raises an exception during the run. |
Source code in data_harness/loop.py
AsyncHarness
data_harness.AsyncHarness
AsyncHarness(
adapter: AsyncProviderAdapter,
system: str,
tools: list[ToolSpec],
max_turns: int = 25,
run_dir: str = "./runs",
cache: SessionCache | None = None,
)
Async variant of Harness. Requires an AsyncProviderAdapter.
Exposes the same run_result / ask_result / run / ask surface as Harness, plus run_stream / ask_stream for token-level streaming.
Source code in data_harness/loop.py
run_stream
async
Stream events for a one-shot run.
Yields StreamEvent objects following the same protocol as the Claude Agent SDK. Each provider turn emits message_start, content_block_start/delta/stop, message_delta, and message_stop events. After the harness dispatches a tool call a ToolResultEvent is emitted. The JSONL logger records fully assembled messages, not individual events.
Source code in data_harness/loop.py
ask_stream
async
Stream events for a follow-up turn in a session.