Model or dataset
MaxGfeller/open-harness avatar
MaxGfeller/open-harness

OpenHarness: an agent harness you assemble in code, not configure

A code-first, composable SDK to build powerful AI agents

616 stars100 forksTypeScriptMIT

At a glance

What is it?
OpenHarness is a TypeScript SDK for building agents on top of Vercel's AI SDK, split into five packages. An Agent is a stateless executor, a Session adds memory plus compaction and retry, and middleware is a function you apply rather than a layer you configure. Two of the packages are React and Vue bindings, and one is an experimental ChatGPT OAuth provider.
Who is it for?
Use OpenHarness if you want the control surface of a coding agent harness, with a runner you assemble, tools you inject, and compaction you configure rather than inherit. Pass if you want a hosted chat product with managed memory, since persistence and hooks are documented but you own the wiring.
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 80 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 October 5, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Five packages, one of them an OAuth provider

The workspace is five packages, and the split tells you where the boundaries are. `@openharness/core` holds the Agent, Session, Conversation, middleware, tools and UI stream integration, so it is the piece everything else depends on.

The other four are alternatives rather than layers. `@openharness/react` and `@openharness/vue` are React hooks and Vue 3 composables with providers, both targeting AI SDK 5 chat interfaces, so pick the one matching your framework and ignore the other. `@openharness/provider-vfs` is a virtual filesystem provider for sandboxed, in-memory or SQLite-backed file access, which is the package that separates a containerised agent from one running against your real disk.

The fifth is the odd one: `@openharness/provider-chatgpt` is described as an experimental ChatGPT and Codex OAuth model provider for local harnesses backed by a ChatGPT subscription. Experimental and OAuth-based in the same line is worth reading twice, since it means the most convenient billing path is also the least settled one.

Agent is stateless, and Session is what remembers

The shortest useful program constructs an Agent with a model, a set of tools and a step limit:

typescript
import { Agent, createFsTools, createBashTool, NodeFsProvider, NodeShellProvider } from "@openharness/core";
import { openai } from "@ai-sdk/openai";

const agent = new Agent({
  name: "dev",
  model: openai("gpt-5.4"),
  tools: {
    ...createFsTools(new NodeFsProvider()),
    ...createBashTool(new NodeShellProvider()),
  },
  maxSteps: 20,
});

Nothing there persists. The Agent is a stateless executor, and `maxSteps: 20` is the ceiling on how many tool round trips a single run may take.

A Session wraps it and adds the things a conversation needs: memory of what was said, compaction when the history gets large, and retry on failure. You give it a context window and then send turns as many times as you like.

typescript
const session = new Session({ agent, contextWindow: 128_000 });

for await (const event of session.send("List all TypeScript files")) {
  if (event.type === "text.delta") process.stdout.write(event.text);
}

The tools are injected rather than discovered, which is the code-first claim made concrete: what the agent can do is literally the object you passed in.

Every call is a for-await over typed events

Whether you call `agent.run` or `session.send`, the return value is an async iterable of events, and you filter by `event.type`. Text arrives as `text.delta` and you write it out yourself.

That shape is the whole streaming contract, and it is why there is no callback API to learn. The same loop works for a terminal program writing to stdout and for a browser component appending to a message list, because the discrimination happens in your loop rather than in the library.

The first example makes the entry point slightly unusual: `agent.run` takes an empty array as its first argument and the prompt as the second, so a stateless run still receives a message history position.

Because events are the interface, anything that consumes streams, including the React and Vue bindings, is built on the same primitive rather than on a parallel one.

Middleware is apply(), and compaction takes its own window

Cross-cutting behaviour is composed by applying functions to a runner:

typescript
import { Conversation, toRunner, apply, withTurnTracking, withCompaction, withRetry } from "@openharness/core";

const runner = apply(
  toRunner(agent),
  withTurnTracking(),
  withCompaction({ contextWindow: 200_000, model: agent.model }),
  withRetry({ maxRetries: 5 }),
);

const chat = new Conversation({ runner });

`toRunner(agent)` turns an Agent into something the middleware can wrap, `apply` stacks the wrappers in order, and a Conversation is the client you then call `send` on. Turn tracking, compaction and retry are each a function you can include or leave out.

One detail is worth noticing because it is easy to misconfigure. The Session example sets `contextWindow: 128_000` while the compaction middleware in the middleware example sets 200_000. Compaction is given a model explicitly as well as a window, so the threshold at which history is summarised is your number, not a library default, and the two examples deliberately do not agree.

Retry is the other half of staying alive: `withRetry({ maxRetries: 5 })` is a policy you can tighten without touching the agent.

Subagents are stateless unless you opt in

Built-in subagents stay stateless by default, which is the conservative choice and matches the Agent design. Three things can be enabled instead.

The first is dynamic subagent catalogs resolved at run time, meaning the set of subagents available is decided during execution rather than declared up front. The second is resumable subagent sessions built on top of Session, so a delegated task can carry history and survive being picked up again. The third is background runs, and those get their own run IDs and session IDs, which is what keeps a background job distinguishable from the conversation that launched it.

The documentation page for subagents covers nested delegation alongside those three, so the intended shape is a parent agent delegating to children which can themselves delegate. The CLI example in the repository ships with subagents enabled, which is the fastest way to see the pattern work.

Three examples, two frameworks, one OAuth flag

The examples are the documentation for the parts the README cannot fit. A CLI agent is a terminal agent with tool approval and subagents, run with `pnpm --filter cli-demo start`. A Next.js demo does streaming chat with `@openharness/react`, run with `pnpm --filter nextjs-demo dev`. A Nuxt demo does the same with `@openharness/vue`, run with `pnpm --filter nuxt-demo dev`.

By default all three use an `OPENAI_API_KEY` environment variable. The CLI agent is the exception, because it accepts ChatGPT and Codex OAuth instead with the `--chatgpt` flag appended to its start command, which is the practical way to try the experimental provider without wiring it into an application.

To run any of them you need the repository and a key in a `.env` file:

bash
git clone https://github.com/MaxGfeller/open-harness.git
cd open-harness
echo "OPENAI_API_KEY=sk-..." > .env
pnpm install && pnpm build

The scripts that exist beyond build and test cover the rest: dev:docs for the documentation site, dev:web for a web package, and format and typecheck across the workspace.

Tests pass with no tests, and releases run through changesets

Two things in the workspace root tell you how the project is run day to day. The test script is `vitest run --passWithNoTests`, so a package with no suite is a green result rather than a failure, and the vitest configuration sits at the root with jsdom as a development dependency for DOM testing.

Releases go through changesets. There is a `.changeset/` directory, the CLI is a development dependency, and three scripts cover the flow: `changeset status` for a look at pending versions, `version-packages` to apply versions and refresh the lockfile without reinstalling, and separate publish and GitHub release scripts under `scripts/release/`.

The recorded releases show the consequence. Rather than one version for the project, each package versions independently, and the newest three are `@openharness/provider-chatgpt` at 0.1.2 on 2026-07-03, then `@openharness/vue` and `@openharness/react` both at 2.0.1 on 2026-06-18. An experimental package sitting at 0.x next to UI packages at 2.x is a good summary of where each one stands. The last push to main is dated 2026-07-17, and the repository also carries `.agents/` and `.claude/` directories alongside RELEASING.md.

Editorial conclusion

Use OpenHarness if you want the control surface of a coding agent harness, with a runner you assemble, tools you inject, and compaction you configure rather than inherit. Pass if you want a hosted chat product with managed memory, since persistence and hooks are documented but you own the wiring. Before you commit, note what the repository does and does not prove: tests run with passWithNoTests, so an empty suite passes, and the last push to main is dated 2026-07-17 with the newest package release a fortnight earlier.

Frequently asked questions

What is OpenHarness?

A code-first, composable TypeScript SDK for building AI agents, based on Vercel's AI SDK and inspired by harnesses like Claude Code and Codex. It is MIT licensed and ships as a pnpm workspace with documentation at docs.open-harness.dev.

How do I build a first agent with OpenHarness?

Install @openharness/core and a model provider, then construct an Agent with a name, a model and tools. Filesystem tools come from createFsTools with a NodeFsProvider and shell access from createBashTool with a NodeShellProvider, and maxSteps caps the tool round trips. Iterate agent.run and handle text.delta events.

What is the difference between an OpenHarness Agent and a Session?

An Agent is a stateless executor that runs one prompt. A Session wraps it and remembers the conversation, handles compaction when the history is too large, and retries on failure. You give a Session a contextWindow, for example 128000.

Can I use a ChatGPT subscription instead of an API key with OpenHarness?

There is an experimental provider for it. @openharness/provider-chatgpt is an experimental ChatGPT and Codex OAuth model provider for local harnesses backed by a subscription, and the CLI example accepts a --chatgpt flag. Examples otherwise use OPENAI_API_KEY.

Does OpenHarness ship UI packages?

Yes. @openharness/react provides React hooks and a provider, and @openharness/vue provides Vue 3 composables and a provider, both for AI SDK 5 chat interfaces. Runnable demos exist for Next.js and Nuxt.

Official sources

  1. License: MIT
  2. MaxGfeller/open-harness on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/maxgfeller-open-harness.svg)](https://hysenlabs.com/projects/maxgfeller-open-harness)