
DeepHarness
Compose LLM agents into typed, concurrent workflows.
A lightweight framework for wiring plain Python functions — and the agents inside them — into a graph that runs branches in parallel, routes on conditions, and threads one typed state object through the whole thing.
Install · Quickstart · Graphs · Providers · Docs · Contributing
Why DeepHarness
- One dependency. That's it.
httpx. Providers talk to vendor REST APIs directly — no vendor SDKs, no transitive dependency sprawl. - Your state is a dataclass. Not an untyped dict. Your editor autocompletes it, your type checker checks it.
- Nodes are just functions. Sync or async, decorated in place. No base class to inherit, no runner object to construct.
- Parallel by default. Independent branches run concurrently via
asyncio; sync functions are offloaded to threads so a blocking call never stalls the event loop. - One provider interface, a dozen vendors.
OpenAI,Anthropic,Gemini, and OpenAI-compatible gateways (Groq,Together,Fireworks,DeepSeek,Mistral,Cerebras,OpenRouter,XAI,Ollama,LMStudio,VLLM) all share the sameLLMinterface.
Install
pip install deepharness
Or from source:
pip install git+https://github.com/sayedshaun/deepharness.git
Requires Python 3.11 or newer.
Quickstart
An Agent runs a think/act loop against a model: ask for a response, dispatch any tool calls
it requests, repeat until the model answers with no tool calls.
from deepharness import Agent, OpenAI, tool
@tool
def get_weather(city: str) -> str:
"""Look up the current weather for a city."""
return f"It is 22°C and sunny in {city}."
agent = Agent(
OpenAI(model="gpt-4o-mini"), # reads OPENAI_API_KEY from the environment
tools=[get_weather],
)
result = agent.run("Weather in Oslo?")
print(result.output)
Every entry point comes in both flavours — run() / arun() and stream() / astream() —
so the same agent drops into a script or an event loop unchanged:
result = await agent.arun("Weather in Oslo?")
Agents as tools
An Agent can itself be handed to another agent as a tool via as_tool(), so one agent can
delegate a task to another:
researcher = Agent(OpenAI(model="gpt-4o-mini"), name="researcher")
editor = Agent(
OpenAI(model="gpt-4o-mini"),
name="editor",
tools=[researcher.as_tool(description="Look up facts on a topic.")],
)
Graphs
For fixed, known-shape workflows — run these steps, some in parallel, then merge — wire plain
functions (or agents) into a Graph instead:
import asyncio
from dataclasses import dataclass
from deepharness import Graph
@dataclass
class State:
sales: str = ""
churn: str = ""
summary: str = ""
graph = Graph(State)
@graph.add(start=True)
async def fetch_sales(state: State) -> State:
await asyncio.sleep(0.5)
state.sales = "Sales up 12% QoQ."
return state
@graph.add(start=True)
async def fetch_churn(state: State) -> State:
await asyncio.sleep(0.3)
state.churn = "Churn down to 4%."
return state
@graph.add(end=True)
def summarize(state: State) -> State:
state.summary = f"{state.sales} {state.churn}"
return state
graph.connect(fetch_sales, summarize)
graph.connect(fetch_churn, summarize)
executor = graph.build()
result = asyncio.run(executor.run())
print(result.summary) # Sales up 12% QoQ. Churn down to 4%.
fetch_sales and fetch_churn both run in the first wave, concurrently. summarize waits
for both before running.
Agent or Graph?
Reach for as_tool() when an LLM should decide at runtime whether and which sub-agent to
call. Reach for Graph when the workflow's shape — which steps, which run in parallel, in
what order — is known ahead of time.
Providers
Pass api_key= explicitly, or leave it out and let the provider read its own environment
variable:
| Provider | Environment variable |
|---|---|
OpenAI |
OPENAI_API_KEY |
Anthropic |
ANTHROPIC_API_KEY |
Gemini |
GEMINI_API_KEY or GOOGLE_API_KEY |
OpenAI-compatible gateways each read their own key — GROQ_API_KEY, TOGETHER_API_KEY,
DEEPSEEK_API_KEY, and so on — and local runtimes (Ollama, LMStudio, VLLM) need no key
at all. Swapping vendors means swapping the constructor; nothing else changes:
from deepharness import Anthropic, Gemini, Groq, Ollama
Anthropic(model="claude-sonnet-4-5")
Gemini(model="gemini-2.5-flash")
Groq(model="llama-3.3-70b-versatile")
Ollama(model="llama3.2") # no key needed
Contributing
Issues and pull requests are welcome. To get a working checkout:
git clone https://github.com/sayedshaun/deepharness.git
cd deepharness
pip install -e ".[dev]"
make test
Two conventions are worth knowing before you open a pull request:
- The dependency budget is one.
httpx, and nothing else at runtime. A feature that needs a third-party library almost always has a standard-library shape — reach for that instead. - Tests must not touch the network. Inject an
httpxclient or transport so the suite stays deterministic and offline.
Run make fmt and make test before pushing; CI runs the same checks on Python 3.11, 3.12 and
3.13. Keep each commit focused on a single change, with a one-line message prefixed by a
Conventional Commits type (feat:, fix:, docs:, and so on).
Full setup, commands, and design conventions live in CONTRIBUTING.md; the rules the code itself is held to are in AGENTS.md.
License
Released under the MIT License — Copyright (c) 2026 Sayed Shaun.
You may use, modify, distribute, and sell this software, in open-source or commercial work, provided the copyright notice and licence text travel with it. It comes with no warranty.
Metadata
Release files for deepharness 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| deepharness-0.2.0.tar.gz | 60.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| deepharness-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 108.7 kB
Release files / deepharness-0.2.0.tar.gz
| Download URL | deepharness-0.2.0.tar.gz |
|---|---|
| Size | 60.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
41bffefddfc0b132e57ad5d8451d7d0d69b4fa1cfd3fda1626f9286c6653c45f
|
|
BLAKE2b-256 checksum How to use checksums |
761549e24ce9f388a6566183a08a8fd73bc64d50e084614a346bda13685768da
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 4, 2026.
Transparency logRelease files / deepharness-0.2.0-py3-none-any.whl
| Download URL | deepharness-0.2.0-py3-none-any.whl |
|---|---|
| Size | 47.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
58f3824c7fca179202fdfd06b7101a37b22a7ac10b9ae9fc74e622e500867e7a
|
|
BLAKE2b-256 checksum How to use checksums |
de31443d32b181fd3458281d3685c7d747aeb933ab899cab29924a5fdc66223c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 4, 2026.
Transparency log