OpenHarness: A TypeScript SDK That Assembles Agents From Middleware Instead of Prompt Templates
A code-first, composable SDK to build powerful AI agents
At a glance
- What is it?
- OpenHarness is an MIT-licensed TypeScript SDK built on Vercel's AI SDK for constructing AI agents in code. Its distinguishing idea is that sessions, compaction, retries and subagents are composable functions you apply to a runner, not behaviour baked into a framework.
- Who is it for?
- Adopt OpenHarness if you are already on Vercel's AI SDK, want agent behaviour expressed as TypeScript you can read and modify, and need React or Vue streaming without a separate server layer.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 60 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The Problem: Agent Frameworks Hide Their Control Flow
Most agent toolkits give you a loop you cannot see inside. You configure a model, attach some tools, and hope the framework's internal decisions about when to compact context, when to retry a failed tool call, and when to hand work to a subagent match what your application needs. When they do not, you are left reading framework source or filing an issue.
OpenHarness takes the opposite position. Its README describes it as a code-first, composable SDK, and the API reflects that. An Agent is described in the documentation as a stateless executor: you give it a name, a model, a tools object and a maxSteps value, and you consume a stream of events from agent.run(). Nothing about conversation history lives inside the Agent. That separation is the whole design. A Session wraps an agent and adds memory, compaction and retry. A Conversation wraps a runner and adds the middleware pipeline. Each layer is opt-in.
The audience is narrow and identifiable. This is for TypeScript developers building agent behaviour into an existing application, typically a Next.js or Nuxt app that already has a chat UI. It assumes familiarity with Vercel's AI SDK, since models are passed in as AI SDK provider instances. It is not aimed at people who want to install a binary and start chatting.
How the Layers Fit: Agent, Session, Conversation, Runner
The architecture is visible in the README's three code samples, and each one adds exactly one concept.
The first sample constructs an Agent directly. Tools come from factory functions: createFsTools(new NodeFsProvider()) and createBashTool(new NodeShellProvider()). The provider objects are the injection point. The core package ships NodeFsProvider and NodeShellProvider, and a separate package, @openharness/provider-vfs, supplies a virtual filesystem provider for sandboxed, in-memory or SQLite-backed file access. Swapping the provider swaps the execution environment without touching the agent definition. That is a real boundary, not a naming convention.
The second sample introduces Session, constructed with an agent and a contextWindow measured in tokens (128_000 in the example). The README states that the session remembers the conversation and handles compaction and retry. This is where statefulness enters, and it enters at a layer above the agent rather than inside it.
The third sample is the one that carries the design thesis. A Conversation is built from a runner, and the runner is produced by apply(toRunner(agent), ...middleware). Three middleware functions appear: withTurnTracking(), withCompaction({ contextWindow: 200_000, model: agent.model }), and withRetry({ maxRetries: 5 }). Each takes configuration and returns something composable. Compaction receives the model as an argument, which means the summarisation step can use a different model than the agent itself.
The data flow is a single async iterable of events. Both agent.run() and session.send() are consumed with for await, and the README checks event.type === "text.delta" before writing event.text to stdout. That event stream is also what feeds the UI packages: @openharness/react and @openharness/vue are described as hooks and composables for AI SDK 5 chat UIs, so the same stream that a CLI prints is what a React component renders.
Getting It Running: Install, Configure, Run an Example
The install line is two packages: npm install @openharness/core @ai-sdk/openai. The AI SDK provider is a peer you choose; OpenHarness does not bundle model access.
To run the bundled examples, the README gives a four-step sequence:
git clone https://github.com/MaxGfeller/open-harness.git cd open-harness echo "OPENAI_API_KEY=sk-..." > .env pnpm install && pnpm build
The repository is a pnpm workspace, which is why the examples are launched with filters rather than from a single script. The CLI agent runs with pnpm --filter cli-demo start, and the Next.js and Nuxt demos with pnpm --filter nextjs-demo dev and pnpm --filter nuxt-demo dev respectively. The README notes that the CLI agent can avoid the API key entirely by passing --chatgpt, which routes through the ChatGPT/Codex OAuth provider instead.
That provider, @openharness/provider-chatgpt, is described in the package table as experimental and is intended for ChatGPT subscription-backed local harnesses. It is the only package in the set still on 0.1.x, at 0.1.2. The React and Vue packages are on 2.0.1. Treat the version gap as a signal about which parts of the project the maintainer considers settled.
Configuration beyond the API key is done in TypeScript, not in a config file. There is no openharness.config.json or equivalent mentioned anywhere in the README. Agent behaviour is the code that constructs it.
Where the Composition Model Costs You
Composability has a price, and the README does not hide it so much as leave it unstated.
The middleware pipeline is ordered, and order matters. apply() takes the runner first and then the middleware, but the README never states whether withCompaction runs before or after withRetry in the example as written, or what happens when a retry fires mid-compaction. If compaction summarises the history and the retry then replays a failed turn, the two interact in a way that a reader cannot determine from the sample alone. This is the kind of question the documentation has to answer, and the README points to docs.open-harness.dev/core/middleware for it. Anyone evaluating the project should read that page before assuming the pipeline behaves like a conventional interceptor chain.
The stateless Agent is also a constraint, not just a virtue. If you construct an Agent and call run() twice expecting continuity, you get none. The README's own progression from Agent to Session exists precisely because the first layer does not remember. Developers used to frameworks where the agent object owns the conversation will find the first sample misleading until they reach the second.
Subagents are the thinnest part of the material. The README says built-in subagents stay stateless by default and that you can opt into dynamic catalogs resolved at run time, resumable subagent sessions built on Session, and background runs with separate run IDs and session IDs. Those are listed as capabilities, not demonstrated. There is no code sample for any of them. Whether background runs survive a process restart, and how a separate run ID is addressed once it is running, are not answerable from the README.
Finally, the tool surface is powerful and that is the point of concern. createBashTool(new NodeShellProvider()) gives a model shell access on the host machine. The README mentions tool approval in the description of the CLI example, and permissions are listed as a documentation topic under Tools, but the core library as shown in the quick start does not gate execution. If you are building anything that runs untrusted input, the VFS provider and the permissions documentation are the two things to examine before the quick start code goes anywhere near production.
How It Differs From Vercel's AI SDK Alone, and From Claude Code
The honest comparison is not against another agent framework. It is against Vercel's AI SDK, which OpenHarness is built on and which its README credits explicitly.
AI SDK gives you model calls, streaming, and tool-calling primitives. It does not give you a session object with compaction, a retry middleware, a virtual filesystem abstraction, or a subagent concept. OpenHarness is the layer above that: it takes the AI SDK's model interface as an input (openai("gpt-5.4") in the example) and supplies the agent runtime around it. The practical difference is that if you adopt OpenHarness you are not replacing your AI SDK code, you are wrapping it. That makes the migration cost low and the lock-in shallow, which is unusual for this category.
The other reference point is Claude Code and Codex, which the README names as inspiration. Those are finished terminal products. OpenHarness is the library you would use to build something shaped like them, and the CLI example under examples/cli is the demonstration of that. If what you actually want is a terminal agent to use today, the CLI example is a starting point you would extend, not a product. The provider-chatgpt package exists specifically so a locally built harness can authenticate against a ChatGPT subscription, which is a direct nod to that Codex-style usage pattern.
On the UI side, @openharness/react and @openharness/vue target AI SDK 5 chat UIs directly. That is a narrower and more concrete claim than most agent SDKs make, and it is verifiable: the packages exist on npm at 2.0.1.
Maintenance, Versioning and the MIT Licence
The repository is not archived and the last push recorded is 2026-07-17. The most recent releases listed are @openharness/provider-chatgpt at 0.1.2 (2026-07-03), with @openharness/vue and @openharness/react both at 2.0.1 (2026-06-18). The React and Vue packages having reached 2.x while the ChatGPT provider sits at 0.1.x tells you the maintainer versions packages independently and is willing to signal instability per package rather than across the project.
That independence is the main upgrade cost. Because the packages are published separately and versioned separately, a minor bump in core can in principle require matching bumps in the UI packages. The README does not publish a compatibility matrix, so pinning exact versions in package.json is the safer default than using carets, at least until you have confirmed which combinations the maintainer has tested.
The licence is MIT, stated in the README and in the repository's LICENSE file. MIT permits commercial use, modification and redistribution with the licence and copyright notice retained. That is permissive and low-friction for closed-source products. It says nothing about the terms of the model providers you connect to, and the ChatGPT OAuth provider in particular sits on top of a consumer subscription; whether using a subscription that way is permitted is a question for the provider's terms, not for this project's licence. Nothing here is legal advice.
The dependency surface is worth checking rather than assuming. The README's badge row links to a libraries.io dependency count for @openharness/core, which is the maintainer inviting you to look. A small core is the stated implication of a composable SDK, and the badge is the claim; verify it against the actual package before you take it on faith.
Editorial conclusion
Adopt OpenHarness if you are already on Vercel's AI SDK, want agent behaviour expressed as TypeScript you can read and modify, and need React or Vue streaming without a separate server layer. Skip it if you want a finished CLI product rather than a library, if your stack is Python, or if you need a stable provider matrix today: the ChatGPT OAuth provider is labelled experimental and the newest published package is still on 0.1.x. Before committing, verify on npm that the package versions you intend to install actually exist at the versions you need, and read the middleware documentation at docs.open-harness.dev/core/middleware to confirm the composition order matches how you expect compaction and retry to interact.
Community notes