Skip to main content

tinyharness: an agent harness you can read in an afternoon. Scout, the tinyharness pony, wears a yellow work harness with a notebook pouch.

PyPI Python versions CI Apache 2.0 license

tinyharness is the loop that turns an LLM into an agent: it reads, searches and edits your code, runs commands in a sandbox, asks before it changes things, and keeps long sessions inside the context window. It works as a coding agent in your terminal and as a Python SDK for building your own. The agent core is about 9k lines of code with no framework underneath, so you can read the whole thing in an afternoon.

A tinyharness session: Scout greets you beside the model, project and sandbox; the agent runs the failing tests, reads invoice.py, fixes the discount bug with a one-line edit, and the tests pass.

Why tinyharness

  • Readable. About 9k lines of code for the agent core (12k with the terminal UI), commented for people learning how agents work. Start with agent/loop.py.
  • Any model, even yours. Anthropic, OpenAI, Gemini, xAI, OpenRouter, DeepSeek, Mistral and Groq, each through its own current API, plus any OpenAI-compatible server: vLLM, Ollama, llama.cpp, SGLang.
  • Robust with small models. Cut-off replies, empty replies, tool calls written as text, malformed arguments and repeated failures are handled by the harness, so mid-size open models get work done too. How.
  • Safe without the friction. Commands run with filesystem and network isolation; routine contained work doesn't ask, while a newly required exact host can be approved without dropping containment. Downloading new executable code asks once; cached tools then run normally, and bare Python installs use a project .venv. API keys never reach the model. Once outside content is in the conversation, anything that could send data out asks a person first.
  • Small and fast. Seven core tools and a short prompt (about 3k tokens before your instructions) that stays the same from call to call, so the provider's cache hits.
  • Extensible. One Python file with setup(th) adds tools, prompt sections, commands and hooks. Web search, PDFs, subagents and parallel teams are built-in extensions you turn on.

Quickstart

Requires Python 3.11+. Install the command with uv (or pipx):

uv tool install tinyharness-lm     # or: pipx install tinyharness-lm
cd your-project && tinyharness

The first run asks which provider to use and for its API key; one key for any supported provider is enough. uv tool upgrade tinyharness-lm updates it.

In the session, type / to autocomplete commands. The ones you'll use most:

Command
/model pick a model from any provider you have a key for
/mode change the permission mode (or press Shift+Tab)
/effort how hard the model reasons, low … max, the same for every model
/skills, /<skill> find or run skills
/extensions what's on and what it costs per request; /extensions on web
/sandbox what commands can write, read and reach
/btw <question> a quick side question, even while the agent works; not added to the conversation
/sessions, /context, /clear switch conversations, inspect context, or start fresh
/key [provider] add or replace a provider key saved in auth.json

Useful flags: -c (continue the latest conversation), -r (pick one), -p "prompt" (run one prompt and exit), --model provider:model, --mode ask, --cwd <dir>.

Use it from Python

uv add tinyharness-lm              # or: pip install tinyharness-lm
import asyncio
from tinyharness import AssistantText, Session, SessionOptions, ToolUse


async def main():
    async with Session(SessionOptions()) as session:
        async for event in session.run("write fib.py with a fib(n) function"):
            match event:
                case AssistantText(text=text):
                    print(text, end="")
                case ToolUse(summary=summary):
                    print(f"\n● {summary}")


asyncio.run(main())

The CLI and the SDK consume the same typed event stream. For headless runs, pass permission_callback= to decide what would otherwise prompt. Extensions can be passed in code (SessionOptions(extensions=[...])) and tested against a scripted model (tinyharness.testing). Subagents can be defined in code (AgentSpec) or Markdown, each on any provider's model, and conversations can be resumed from code (SessionOptions(resume="latest")). Post-turn evaluators can be passed with SessionOptions(evaluators=[...], evaluator_timeout=30.0); their bounded results are appended to the versioned trace without changing the completed conversation. See examples/ for a one-shot query, a multi-turn session, resuming, a custom tool, a permission policy with hooks, skills, subagents on different models, an extension, and a post-turn evaluator.

What's inside

Agent loop Streaming, parallel read-only tools, argument validation and repair, recovery from cut-off and empty replies, retries with backoff
Tools Core: Read Write Edit Glob Grep Bash Open. Built-in extensions you turn on: web (WebSearch, WebFetch), pdf, todo, agents (Agent, Team)
Extensions One Python file with setup(th) adds tools, prompt sections, slash commands and event handlers (docs/extension-api.md); `/extensions on
Permissions Routine work in the project and the sandbox doesn't ask; anything beyond it asks, with a diff for edits. Modes default, auto (an LLM classifier for unclear cases), plan, ask (every edit and command) and bypassPermissions; persistent allow/deny rules
Sandbox Native Seatbelt on macOS; optional Sandbox Runtime on Linux, Windows and as a fallback. Commands have narrow writes, blocked credential reads (SRT covers the home and project neighborhoods) and allowlisted network access; /sandbox shows it
Context Cap oversized messages, stub old tool output, then summarize, without rebuilding the prompt cache every turn
Subagents and teams Isolated contexts on any model; Team runs a dependency graph of tasks; the model picks the team's size, up to 20 subagents at once (max_parallel_agents)
Skills, hooks, plugins Portable SKILL.md skills, shell or Python hooks on 5 events, and plugins that bundle them
Resume & traces Lossless, schema-versioned JSONL logs outside your repo with correlated main-loop model/tool decisions and latency; on resume, the agent is told what changed while you were away

Each of these in detail: docs/features.md.

How it works

 CLI (cli/) ──┐
              ├──► Session (session.py) ──► agent loop (agent/loop.py) ──► LLM (llm/client.py ─ llm/adapters/)
 SDK ─────────┘        wires components          │                           retries, model lists
                                                 ├─ compaction (agent/compaction.py)
                                                 ├─ runaway / empty-reply recovery (agent/runaway.py)
                                                 ├─ permission gate (permissions/gate.py) ── hooks
                                                 └─ tools (tools/*.py) ── workspace, sandbox + allowlist proxy
                                                      │
                                                      └─ extensions (ext.py, extensions/) add tools,
                                                         prompt sections, commands and hooks; the agents
                                                         extension's Agent / Team ──► subagents
                                                         (subagents/runner.py): the same loop, fresh
                                                         history, any provider:model

Suggested reading order:

# File What you'll learn
1 agent/loop.py The core loop: stream the model, validate calls, gate them, run them, repeat
2 tools/base.py, tools/read.py How tools are defined and exposed to the model
3 permissions/gate.py The layered permission check
4 agent/compaction.py Staying inside the context window without breaking the prompt cache
5 agent/history.py The conversation, and the normalization every request goes through
6 session.py Wiring it all together; state that persists between prompts
7 llm/adapters/ One small adapter per provider API; the rest of the code never sees a provider's format
8 ext.py, extensions/ How a feature plugs in: the built-in extensions use the same API as yours
9 subagents/runner.py Subagents are the same loop with a fresh history
10 cli/ Rendering events (render.py), the REPL, slash commands; colors in theme.py, Scout in scout.py

Every prompt sent to the model is a Markdown file in tinyharness/prompts/.

Docs

Development

git clone https://github.com/anujdutt9/tinyharness && cd tinyharness
uv sync                   # installs dev tools too
uv run tinyharness
uv run pytest             # tests use a scripted fake LLM; no API key needed
uv run pytest --terminal  # also the screen-layout tests, in an emulated terminal (slower)
uv run ruff check . && uv run ruff format --check . && uv run mypy
uv build && uv run python scripts/check_dist.py   # build and smoke-test the package

Releasing: bump __version__ in tinyharness/version.py, add a CHANGELOG entry, then push a matching tag (git tag v0.2.0 && git push --tags). The release workflow tests, builds and publishes to PyPI.

License

Apache License 2.0.

Metadata

Release files for tinyharness-lm 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for tinyharness-lm 0.1.0
File Size Uploaded Provenance Actions
tinyharness_lm-0.1.0.tar.gz 3.0 MB Without provenance Details for tinyharness_lm-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for tinyharness-lm 0.1.0
File Interpreter ABI Platform Actions
tinyharness_lm-0.1.0-py3-none-any.whl Python 3 none any Details for tinyharness_lm-0.1.0-py3-none-any.whl

Total release size: 3.3 MB

Release files / tinyharness_lm-0.1.0.tar.gz

Download URL tinyharness_lm-0.1.0.tar.gz
Size 3.0 MB
Tags Source
SHA-256 checksum
How to use checksums
37e1b20aa787d8232baaac454ce75451fef862963ccc576d62009619a0a6cac2
BLAKE2b-256 checksum
How to use checksums
8f8ec72fcc87df3c62e06ff45ed14d3de01827c16be6bb4e3467105f808a0294
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / tinyharness_lm-0.1.0-py3-none-any.whl

Download URL tinyharness_lm-0.1.0-py3-none-any.whl
Size 314.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
cac0f3f5338c81753ddadd95b0bb40844efc5e9ffa3431dc4a1c079b850ccd61
BLAKE2b-256 checksum
How to use checksums
1bb5b1c7755c0a054aababe5adc33c14ca5097ba7e9052708a6a0b609b5f34aa
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for tinyharness-lm 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page