Minimal filesystem agent harness with provider-backed Responses-like models.
Project description
A minimal, opinionated agent harness —
focused scope, straightforward code, easy to fork.
Why this exists
ThinHarness is for building agents with a defined job and a bounded set of tools, using a refreshingly simple framework that's easy to inspect and customize.
Production agents rarely stop at framework configuration. Things like orchestration, permissions, user/session storage, and deployment become specific to the application and its users.
ThinHarness exists for the gap between building the agent loop yourself and adopting a large agent runtime where the loop comes bundled with assumptions you don’t need and can’t easily change.
It owns a focused set of agent-loop primitives that generalize well and are tedious to rebuild, leaving the rest of the application stack for you to own.
I started building ThinHarness after running into this gap in practice. Filesystem-enabled agents are simple yet powerful, but you mostly get them by adopting a large framework with layers of abstraction. I usually needed only a small slice of the functionality, but that slice came with coupled assumptions that didn't match my application. Making it fit meant writing enough wrappers, adapters, and fixes that I ended up owning framework-shaped code anyway.
| Library | LOC1 | Tool retries2 |
Subagents | Skills | FS tools |
OTel tracing |
| ThinHarness | 7,935 | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
8,2633 | ❌ | ✅ | ✅ | ✅ | ⚠️ |
|
|
9,840 | ❌ | ✅ | ❌ | ❌ | ⚠️ |
|
|
17,6644 | ❌ | ✅ | ✅ | ✅ | ❌ |
|
|
32,526 | ⚠️ | ✅ | ✅ | ❌ | ✅ |
|
Agent Framework |
41,331 | ❌ | ✅ | ✅ | ✅ | ✅ |
|
|
59,087 | ✅ | ❌ | ❌ | ❌ | ✅ |
|
|
65,799 | ⚠️ | ✅ | ✅ | ✅ | ✅ |
|
|
73,796 | ❌ | ✅ | ✅ | ⚠️ | ⚠️ |
|
|
113,477 | ⚠️ | ✅ | ✅ | ✅ | ⚠️ |
* Table focuses on harness-level features that differentiate the libraries. All listed also support MCP, lifecycle hooks, multi-turn conversations, structured output, and human-in-the-loop. It intentionally does not compare framework/platform features like vector DB integrations, hosted deployment, memory/session stores, or broad SaaS connectors.
1. LOC excludes anything that is not the core agent harness framework. See raw README source comments for exact commands.
2. Tool retries: a documented primitive (e.g. Pydantic AI's ModelRetry) that lets tools signal "model passed bad args — retry with this feedback," distinct from generic exception propagation.
3. Claude Agent SDK shells out to the Claude Code CLI binary, which is 200k+ LOC.
4. deepagents is a thin wrapper over LangChain/LangGraph; effective import surface is ≈112k LOC.
See docs/table.md for per-cell rationale and how the LOC numbers are measured.
Opinions
ThinHarness has opinions. They are the reason it stays small.
Purpose-built agents, not universal agents. ThinHarness is for bounded agent loops, not open-ended interactive assistants like Claude Code or OpenClaw. For business use cases, focused agent loops orchestrated by deterministic code are usually a better fit than sprawling multi-agent systems with broad authority.
No bash by default. Purpose-built business agents usually don't need a shell. Bash is a broad security and reliability surface: it gives the model open-ended authority instead of typed, bounded actions. ThinHarness keeps bash out of the default and built-in tool sets, but exposes an opt-in BashTool for exploratory runs before the workflow is hardened with typed tools.
Search is a top priority. The search tool exposes ripgrep as compact grouped path/line results, tuned for document and business-workflow agents rather than code navigation. There's also a jsonl_search variant, because JSONL is the right shape when you're replacing RAG with agent-driven search over structured data: ripgrep row prefiltering, jq-style field projection, where filters, range filters, and snippets from large multiline fields.
Parallel LLM calls, built in. Fan out from inside the harness when a workflow needs efficient parallel processing or majority vote for reliability. Set builtin_parallel_llm_model to enable the default parallel_llm tool for plain-text batches; for validated structured output per call, instantiate ParallelLlmTool yourself with output_type (a Pydantic model). Each call is stateless, and large batches can write JSON to output_file.
No token streaming. Streaming is for workflow progress, not live chatbot text. ThinHarness emits run, model-turn, tool, retry, limit, and subagent events, but it does not stream provider token deltas. Token streaming would add provider-specific plumbing, event merging, cancellation edge cases, and more surface area to keep stable. For workflow-style agents, step-level updates are usually the useful signal.
Three providers, no matrix. ThinHarness ships small provider classes for OpenAI, Anthropic, and OpenRouter. If your gateway speaks one of those protocols, you swap a base URL and move on. If not, the provider classes are small enough to fork or replace, and ignoring the bundled ones costs you nothing
No compaction. Compaction is a workaround for context windows filling up across long, accumulating runs — useful for interactive coding sessions that sprawl over hours. For SDK-based business agents, the right answer to "context is getting big" is almost always better task decomposition: shorter runs, separate harness instances, narrower subagents.
No deployment layer. Agents still need serving, auth, durable jobs, user/session storage, and deployment in production. ThinHarness does not try to own that stack. A bundled deployment layer might work for some teams, but it will miss plenty of real production shapes; instead of adding more code and more options, ThinHarness leaves that application stack for you to own.
Install
uv add thinharness # or pip install thinharness
Requires Python 3.11+.
Use
import asyncio
from thinharness import Harness, HarnessConfig
async def main():
async with Harness(HarnessConfig(root=".", model="openai:gpt-5.5")) as harness:
result = await harness.run("Read README.md and summarize it.")
print(result.text)
asyncio.run(main())
There's a synchronous wrapper too: Harness(...).run_sync(...).
For workflow visibility, use Harness.stream(...):
from thinharness import RunCompletedEvent
async for event in harness.stream("Process these records."):
if event.kind == "tool_call_started":
print(event.tool_name)
if isinstance(event, RunCompletedEvent):
result = event.result
Streaming emits coarse run, model, tool, retry, limit, and subagent events, then finishes with the same HarnessResult returned by run().
Features
- Filesystem tools:
read,write, batched exact-replacementedit,search,list, andglobwith root-scoped path policies. - JSONL search: opt-in
jsonl_searchfor structured line-delimited data, with ripgrep prefiltering, field projection, equality/contains/regex/rangewherefilters, and field-level snippets from large multiline string values. - Bash prototype tool: opt-in
BashToolfor exploratory shell commands. It is lightweight, custom-registration only, and is not included in the default or built-in tool set. - Provider adapters: built-in OpenAI, Anthropic, and OpenRouter adapters, plus public model/session protocols for implementing another provider.
- Custom typed tools: define sync or async
ToolSpechandlers with Pydantic argument models, normalizedToolResultenvelopes, sequential/approval flags, and per-tool retry settings. - Structured output: Pydantic-validated results with native, tool, prompted, and text modes.
- Hooks: lifecycle and tool-call interception for prompt submission, tool calls, subagents, limits, and run boundaries.
- Subagents: opt-in delegation through a built-in
subagenttool and explicitSubAgentConfig. - Parallel LLM: opt-in
parallel_llmfan-out for batches of independent one-shot prompts, plusParallelLlmTool(...).spec()for renameable tools with explicit model, path, prompt, and retry settings. - Skills: explicit
skill_readandskill_runtools for selected skill directories, with Python, shell, JavaScript, and Go script runners. - Resume: clean new-turn continuation through self-contained transcript state that can replay across built-in providers and models, preserving native reasoning on same-provider resume and degrading it to text across providers.
- MCP: optional MCP client support with lazy tool discovery and collision checks.
- Parallel tool calls: same-turn tool batches run concurrently when every called tool is parallel-safe.
- Human approvals: mark custom tools as approval-required so a run pauses before side effects, returns pending call details plus resume state, then continues after an approve/reject decision.
- Event streaming: async coarse-grained run, model, tool, retry, limit, and subagent events for workflow visibility.
- Tool retries: tools raise
ModelRetryto send structured feedback back to the model and trigger a retry within a per-tool budget. - Limits and notices: configured request, tool-call, output-retry, and tool-retry budgets bound each run; near-limit guidance can warn the model before request or tool-call budgets are exhausted.
- Tracing: local plaintext JSONL traces plus OpenTelemetry-compatible spans for runs, provider calls, tools, and subagents.
Examples
Three agents built on ThinHarness, from a self-contained demo to a benchmark run to one I use live.
1. Web Research Report
A market-landscape research agent that plans, runs batched Exa search, triages and fetches sources, extracts structured source notes with parallel_llm, drafts a report, and runs a citation_critic subagent before finalizing.
It isn't meant to be a state-of-the-art research agent; it's a worked example showing the harness drive a non-trivial agentic loop correctly — real multi-tool use across 15 model turns, ending in a reasonable cited report. The full run is browsable on the docs site.
2. LongMemEval-V2 Reproduction
I ran ThinHarness on a retrieval-heavy 127 question subset of a benchmark for long-term agent memory, and did a local reproduction of the benchmark's optimized harness on the same subset.
- Performance: Matched-or-better accuracy (74.0% vs 72.4% on the 127 dynamic questions) with ~46% less token usage (62M vs. 116M). See my fork for more details.
- Simpler Setup: ThinHarness only had its built-in filesystem tools (with
jsonl_searchdoing the heavy lifting), while the benchmark harness was a full Codex instance with shell and a custom Python tool designed for the task.
3. Personal Opinions Agent
An agent I run live for myself, inspired by this Substack post. On a schedule, it reads my Readwise highlights + surrounding context, draws on what it's learned in past runs, and proposes conceptual changes to a durable OPINIONS.md.
Approval happens over Telegram, and it can be simple accept/reject or involve multi-turn revision + discussion. Under the hood it exercises filesystem tools over a JSONL corpus, native structured output, long-term memory, a custom validation tool, and resumable conversations that pick up across each round. See here for the code.
Status
Pre-1.0. APIs may shift, but I don't expect dramatic changes. Forking is a real option, not just a theoretical one: the codebase is small enough that pulling upstream changes into your fork by hand stays cheap. Each major feature (MCP, subagents, jsonl_search, parallel_llm, skills) lives in its own file with no hidden dependencies. If you don't use one, that's even less code to worry about. If you want to delete it entirely, that's a one-shot 10-word prompt to a coding agent.
ThinHarness was built with coding agents, but isn't vibe-coded. I have used it, iterated on it, and reviewed its design + behavior. The website includes a codebase explainer that has gone through many versions so my mental model of the codebase stays up-to-date.
License
MIT. See LICENSE.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file thinharness-0.5.3.tar.gz.
File metadata
- Download URL: thinharness-0.5.3.tar.gz
- Upload date:
- Size: 116.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6996f2669680ecb55835b23c33b9e5c16f604a6a87629fac70702ad2c9b0dcdc
|
|
| MD5 |
3828718a998fea3f00065e8b07d1cfa2
|
|
| BLAKE2b-256 |
5db4cf24f1c86bf753b569092a2fe8cf092147f1d3b82126fc538e67fa8a3b0b
|
Provenance
The following attestation bundles were made for thinharness-0.5.3.tar.gz:
Publisher:
release.yml on ryanbbrown/thinharness
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
thinharness-0.5.3.tar.gz -
Subject digest:
6996f2669680ecb55835b23c33b9e5c16f604a6a87629fac70702ad2c9b0dcdc - Sigstore transparency entry: 2092555144
- Sigstore integration time:
-
Permalink:
ryanbbrown/thinharness@f2ee8c4c2fab2fa697824d6646cdf12c900e9efb -
Branch / Tag:
refs/tags/v0.5.3 - Owner: https://github.com/ryanbbrown
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@f2ee8c4c2fab2fa697824d6646cdf12c900e9efb -
Trigger Event:
push
-
Statement type:
File details
Details for the file thinharness-0.5.3-py3-none-any.whl.
File metadata
- Download URL: thinharness-0.5.3-py3-none-any.whl
- Upload date:
- Size: 104.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
833465ba96fff2e43e60d3ccf5bdd463f17650d27b795d54c4bf2c2e7cc36e27
|
|
| MD5 |
b93d6b123300b9af5bdc2b6b00cde359
|
|
| BLAKE2b-256 |
674041e597a38cefa5689570a9ad7965878879d8fac75592539804c00924dea8
|
Provenance
The following attestation bundles were made for thinharness-0.5.3-py3-none-any.whl:
Publisher:
release.yml on ryanbbrown/thinharness
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
thinharness-0.5.3-py3-none-any.whl -
Subject digest:
833465ba96fff2e43e60d3ccf5bdd463f17650d27b795d54c4bf2c2e7cc36e27 - Sigstore transparency entry: 2092555221
- Sigstore integration time:
-
Permalink:
ryanbbrown/thinharness@f2ee8c4c2fab2fa697824d6646cdf12c900e9efb -
Branch / Tag:
refs/tags/v0.5.3 - Owner: https://github.com/ryanbbrown
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@f2ee8c4c2fab2fa697824d6646cdf12c900e9efb -
Trigger Event:
push
-
Statement type: