AI Harnesses¶
A DIY exploration of AI agent harnesses and trade-off analysis. Each harness
here re-expresses patterns from
agentic-design-patterns
— hand-built LangGraph implementations of Anthropic's agentic design
patterns — through a real framework, so you can see side by side what the
framework supplies for free and what it costs you.
No API key required
Everything here runs and is unit-tested offline, against a small deterministic fake chat model. Point it at a real model with one environment variable when you want to.
What's here¶
-
Harnesses and Loops
The conceptual essay behind this repo: the agent loop vs. the harness around it, harness archetypes, and what production adds beyond the graphs — memory, guardrails, MCP tool integration, and evals.
Five harnesses re-express the same two patterns — orchestrator_workers
and human_in_the_loop — through five different frameworks:
| Harness | Summary |
|---|---|
| deepagents | LangChain's agent-harness library, built on LangGraph |
| OpenAI Agents SDK | OpenAI's own runtime — handoffs, guardrails, tool-approval |
| Microsoft Agent Framework | Workflow executors/edges + Magentic multi-agent orchestration |
| Strands Agents SDK | AWS's code-first framework — Agents-as-Tools, HumanInTheLoop |
| Dapr Agents | Built on Dapr's durable Workflow engine — needs a real sidecar |
See the full comparison — runtime substrate, delegation mechanism, HITL mechanism, and offline-fake strategy, side by side.
Quick start¶
uv sync
uv run python -m deepagents_harness.run # one harness, both patterns
uv run python compare.py # every installed harness, side by side
uv run pytest -v
Both print a structured trace — harness name, the fan-out owner, the
runtime-decided subtask count, the gate mechanism, how resume works — in
one shared vocabulary (shared/trace.py), so a run lines up cleanly
against its vanilla equivalent in
agentic-design-patterns
and against the other harnesses. compare.py stacks all installed
harnesses under aligned headers; a missing one is a labelled skip, not an
error. Add --otel (after uv sync --group otel) for the same run as an
OpenTelemetry span tree — see docs/observability.md.
Runs fully offline against a small deterministic fake chat model — no API
key, no network. Point it at a real model with the LLM_PROVIDER
environment variable (anthropic or openai, plus the matching API key)
when you want to.
What is deliberately not here¶
The patterns are not exposed as MCP tools. It would be technically easy and conceptually wrong. MCP exists to give an agent capabilities — query this database, deploy this service. These are architectural shapes, not capabilities: a tool that just runs a toy graph sorting a fake support ticket helps nobody and teaches nothing about the pattern it wraps.
CLI coding agents are not integrated, only documented. OpenCode, Goose,
Hermes Agent, Crush, Cline and Aider are harnesses in their own right —
per docs/harnesses-and-loops.md, "a
harness is not an eighth pattern; it's the composition of these seven."
They already use these patterns internally; feeding patterns into them
inverts the relationship. And their config surface moves fast enough that
pasted, untestable snippets would be quietly wrong within months — so that
guidance lives in prose, which can say "check the current docs," and not in
this repo, which can't be CI-verified.
The same two patterns, across every framework here — on purpose. A
single harness picking one framework mirrors production, where nobody
re-implements a workflow across three frameworks; they pick one. A repo
comparing frameworks needs the opposite move: hold the pattern fixed and
vary the machinery underneath it. orchestrator_workers and
human_in_the_loop were chosen because they're the two patterns every
framework surveyed here expresses natively — see each harness's own
README for the ones that don't fit, and why.
Adding another harness¶
The bar, mirroring agentic-design-patterns' "everything runs and is
tested with no API key" guarantee:
- It must be drivable offline by a stand-in model. For anything built
on LangChain this means accepting a
BaseChatModel, soshared/llm/fake.py'sFakeChatModeldrops in. A harness that can only talk to a live endpoint belongs in docs, not here. (dapr_agents_harnessis this repo's one accepted exception — not a live endpoint, but a real local Dapr sidecar, required to exercise durable workflow state honestly. See its README for why that was worth the exception.) - Implement only the patterns it expresses natively, and document the non-fits — those explain more than the fits do.
- Follow the existing module conventions:
build_agent()alongside arun.pyexposingmain(...) -> dict, plusdescribe_orchestrator_workers(task)anddescribe_human_in_the_loop()that adapt the framework's result intoshared/trace.py'sOrchestratorTrace/HitlTrace(this is whatcompare.pyand therun.py__main__render); and tests undertests/that stay green with a plainuv sync. - Add the dependency to
pyproject.tomland regenerateuv.lock— CI runsuv sync --lockedand will fail on drift.
Project layout¶
ai-harnesses/
├── compare.py # run one pattern across every installed harness, side by side
├── deepagents_harness/ # patterns under LangChain's deepagents
│ └── tests/
├── openai_agents_harness/ # patterns under the OpenAI Agents SDK
│ └── tests/
├── agent_framework_harness/ # patterns under Microsoft Agent Framework
│ └── tests/
├── strands_harness/ # patterns under the Strands Agents SDK
│ └── tests/
├── dapr_agents_harness/ # patterns under Dapr Agents (needs a real sidecar; see its README)
│ ├── components/ # Dapr component config (in-memory, actor-capable state store)
│ └── tests/
├── shared/ # vendored LLM factory + fake model + basic tools
│ ├── llm/ # LangChain-specific -- reused only by deepagents_harness
│ ├── tools/
│ ├── trace.py # one trace vocabulary every harness reports itself in
│ └── obs.py # opt-in OpenTelemetry wiring for `--otel`
├── docs/ # mkdocs site (Harnesses and Loops essay, comparison, observability, one page per harness)
└── mkdocs.yml
License¶
MIT — see LICENSE.