eharness
Build your own agent harness on the Vercel AI SDK v7: plugins, context, messages, streaming, sessions and compaction, with storage you plug in.
Power your agent
Yayıncının kendi komutu. PlugMCP uç noktası değildir.
Host a copy with our keys and logs.
npx -y [email protected]Claude Code'un yerel komutlar için kurulum bağlantısı yok. Aşağıdaki komutu kopyalayın ve bir terminalde çalıştırın.
claude mcp add eharness -- npx -y eharness@0.9.0Bir terminalde çalıştırın.
{ "mcpServers": { "eharness": { "command": "npx", "args": [ "-y", "[email protected]" ] } }}claude_desktop_config.json
Uzak URL mi, yoksa makinenizde bir komut mu? İki kurulum arasındaki fark
PlugMCP’de çalıştır
A new server you create. It is not this listing's official endpoint. Use the publisher install when you want their URL and their credentials.
BaşlatYayıncıdan
Build your own agent harness on the Vercel AI SDK v7: plugins, context, messages, streaming, sessions and compaction, with storage you plug in.
Build your own agent harness on AI SDK v7: plugins, context, messages, streaming, sessions, compaction.
Every agent built on AI SDK ends up writing the same harness around streamText: a step loop,
prompt assembly, skills, MCP, history that survives restarts, compaction, approvals, a UI stream.
eharness is that skeleton as a library. You keep your server, auth, database and domain logic;
it standardises the parts every harness reimplements and stays out of the rest.
Status. The contracts in docs/specs are implemented and tested. Before 1.0,
breaking changes ship only in minor versions with a migration note in the
changelog and in Upgrading
(API stability). Modules marked Draft below may change in a
minor release.
useChat
as is), messages are UIMessages, tools are tool(), models are LanguageModel. No parallel
types.MessageAdapter, a StateAdapter, an optional durable
inbox. The library ships memory adapters and conformance suites, so your Postgres, JSON or Redis
adapter is provably correct.How it compares:
More in concept (what eharness is and is not, principles, glossary).
npm install eharness ai zod # or: bun add / pnpm add
npm install @ai-sdk/mcp # only if you use eharness/mcp
Node ≥ 22 or Bun. ESM only. ai and zod are peer dependencies; eharness has no runtime
dependencies of its own.
import { tool } from 'ai'
import { defineHarnessAgent, defineSkill } from 'eharness'
import { filesystem } from 'eharness/filesystem'
import { inMemoryFs } from 'eharness/filesystem/memory'
import { z } from 'zod/v4'
const agent = defineHarnessAgent({
model: 'anthropic/claude-sonnet-4.6', // AI Gateway id or any AI SDK model
contextWindow: 200_000,
instructions: 'You are a careful assistant. Keep notes as files under /notes.',
tools: {
get_time: tool({
description: 'Current time as an ISO string',
inputSchema: z.object({}),
execute: async () => new Date().toISOString(),
}),
},
skills: [
defineSkill({
name: 'note-style',
description: 'Use before writing a note.',
content: 'Bullets only.',
}),
],
plugins: [filesystem({ fs: inMemoryFs() })],
})
const session = agent.session('demo')
const run = session.send('Write the current time to /notes/now.md')
for await (const chunk of run.stream) {
if (chunk.type === 'text-delta') process.stdout.write(chunk.delta) // or, in a route: await run.toResponse()
}
const result = await run.result // never rejects: errors end the turn with stop: 'error'
console.log(`\n${result.stop} after ${result.steps} steps`)
if (result.stop === 'error') console.error(result.error?.code, result.error?.message) // e.g. EH_PROVIDER_AUTH: set AI_GATEWAY_API_KEY
console.log(`${(await session.messages()).length} messages stored`)
The model string goes through the AI Gateway
(AI_GATEWAY_API_KEY); any AI SDK provider model (anthropic('claude-sonnet-4-6'), ...) works too.
This code is examples/quick-start.ts; bun examples/quick-start.ts
runs it offline with a scripted model. Walkthrough: getting started.
Terms such as turn, step, kind message and commit point are in the
glossary.
Each snippet is small; the guide linked under it has the full version. Values marked
// yours: ... are your application's.
useChatimport { handleChatRequest } from 'eharness'
import { agent } from './agent' // yours: the agent from the quick start
declare function authenticate(req: Request): Promise<{ id: string }> // yours
declare function userOwnsChat(userId: string, chatId: string): Promise<boolean> // yours
// POST /api/chat: send, approval answers, regenerate and edit in one route
export async function POST(req: Request): Promise<Response> {
const user = await authenticate(req) // eharness knows only the session id
const body = await req.json() // useChat's { id, messages, trigger, messageId }
if (!(await userOwnsChat(user.id, body.id))) return new Response(null, { status: 404 })
return await handleChatRequest(agent.session(body.id), body, {
runtime: { userId: user.id }, // per-request values go into the turn, not the cached session
}).toResponse() // waits until the turn is accepted, so a failure gets its HTTP status
}
// GET /api/chat/[id]/stream: useChat({ resume: true }) replays the running turn
export async function GET(
req: Request,
{ params }: { params: Promise<{ id: string }> },
): Promise<Response> {
const { id } = await params
const user = await authenticate(req)
if (!(await userOwnsChat(user.id, id))) return new Response(null, { status: 404 }) // same check
const run = agent.session(id).attach() // the running turn, or undefined when idle
return run ? run.toStreamResponse() : new Response(null, { status: 204 })
}
On the client: useChat<InferHarnessUIMessage<typeof agent>>() types every data part, tool and
message kind. Runnable: examples/next-route.ts (offline demo:
bun examples/next-route.demo.ts). Guides: HTTP server,
getting started.
Which one: handleChatRequest or createHarnessHttp?
In a POST route use await run.toResponse(): a failure before the turn is accepted (busy, bad
input) answers 409 / 400 / 503. run.toStreamResponse() streams at once and carries such
failures inside a 200 stream; use it for resume only.
Write a tool as (ctx) => tool() to get the session, the turn, your runtime values and state, and
a typed writer for custom UI data parts:
import { tool } from 'ai'
import { defineDataPart, defineHarnessAgent } from 'eharness'
import { z } from 'zod/v4'
const agent = defineHarnessAgent({
model: 'anthropic/claude-sonnet-4.6',
dataParts: {
invoice: defineDataPart({ schema: z.object({ total: z.number(), status: z.string() }) }),
},
tools: {
create_invoice: (ctx) =>
tool({
description: 'Create and send an invoice',
inputSchema: z.object({ total: z.number() }),
execute: async ({ total }, { toolCallId }) => {
ctx.state.set('lastInvoice', total) // persisted with the session
ctx.stream.data('invoice', { total, status: 'sent' }, { id: toolCallId }) // typed
return `Invoice sent for ${String(ctx.runtime.userId)}` // agent.session(id, { runtime })
},
}),
},
})
Guides: getting started,
rendering data parts. Runnable:
examples/tool-context.ts.
Tools declare a risk; the agent decides which risks need a person. respond() records who
answered, and one hook sees every decision:
import { tool } from 'ai'
import { defineHarnessAgent } from 'eharness'
import { z } from 'zod/v4'
const agent = defineHarnessAgent({
model: 'anthropic/claude-sonnet-4.6',
tools: {
delete_record: tool({
inputSchema: z.object({ id: z.string() }),
metadata: { risk: 'destructive' }, // 'read' | 'write' | 'destructive' | 'external'
execute: async ({ id }) => `Deleted ${id}`,
}),
},
approval: { risk: { destructive: 'user-approval', unknown: 'user-approval' } },
})
const session = agent.session('ops-1')
const { pending } = await session.send('Delete record r1').result // stop: 'tool-pending'
await session.respond({
approvals: (pending?.approvals ?? []).map((p) => ({ id: p.approvalId, approved: true })),
}).result
With useChat, approvals need no server code: handleChatRequest calls respond(). Guides:
approvals and interaction,
permissions, approval guard.
Turns run up to 500 steps. A progress guard stops a turn that repeats itself or keeps failing
(stop: 'stuck'), and when the step budget runs out the model gets one tool-less step to
summarize. With prices, every turn records its estimated cost and USD budgets are enforced:
import { defineHarnessAgent, modelsDevCatalog } from 'eharness'
declare const modelsDevJson: unknown // yours: models.dev's api.json, fetched and cached by your app
const agent = defineHarnessAgent({
model: 'anthropic/claude-sonnet-4.6',
models: modelsDevCatalog(modelsDevJson), // context window and prices (or your own record)
budget: { maxTurnUsd: 2, maxSessionUsd: 20 }, // used up: stop 'cost-cap'
})
const result = await agent.session('job-1').send('Migrate the test suite').result
console.log(result.stop, result.usage.costUsd) // 'complete' | 'stuck' | 'max-steps' | 'cost-cap' | ...
Guides: long-running turns, models and cost, todos.
import { defineHarnessAgent } from 'eharness'
import type { InboxAdapter, MessageAdapter, StateAdapter } from 'eharness'
declare const messages: MessageAdapter // yours: Postgres, JSON files, ...
declare const state: StateAdapter // yours
declare const inbox: InboxAdapter // yours (optional): queue, steer, wake and abort across instances
const agent = defineHarnessAgent({
model: 'anthropic/claude-sonnet-4.6',
storage: { messages, state, inbox },
recovery: { instanceId: 'web-1' }, // a stable id: a restarted process recovers its turns at once
})
const session = agent.session('chat-1')
await session.enqueue('One more thing...', { mode: 'collect' }) // any instance
await session.abort() // stops the turn even when another instance runs it
Guides: writing a storage adapter, several instances, production patterns. More snippets (external waits, browser tools, guard, ledger, memory, background events) are in getting started.
Every package.json#exports entry. Draft means all symbols of the subpath are experimental and
may change in a minor release (API stability). Node-only
subpaths import node: built-ins; everything else is runtime-neutral.
The full table with source files is in architecture.
Every shipped module serves three product shapes: an autonomous server (no human; policy and budgets), a single-process interactive app (CLI or TUI), and a split web/server app (pending state in storage, answers from any instance). See ADR-0034.
Every example runs offline (bun examples/<file>) and is typechecked and executed in CI, except
next-route.ts (route handlers: run next-route.demo.ts instead), the Postgres examples (they
need DATABASE_URL and skip otherwise) and the two apps, team/ and coder/.
Start with which module do I need? (the 33 entry points in three groups, with stability labels), then getting started and the guide index (every guide, one line each). Often needed:
Reference: concept (incl. glossary) · architecture · specs (the contracts) · decisions (ADRs) · contributing
eharness checks message shapes, approvals and file paths; identity and authorization are yours.
Check session ownership in every route (including the resume GET), pass per-request identity
in SendOptions.runtime, add per-user quotas on top of the per-session budgets, and set
toolErrorText and logger before production. The full list of design-level risks, defaults and
what to do: production patterns → security.
A new AI SDK major needs a new eharness minor (0.x) / major (≥ 1.0).
Build your own agent harness on AI SDK v7: plugins, context, messages, streaming, sessions, compaction.
{ "mcpServers": { "eharness": { "command": "npx", "args": [ "-y", "[email protected]" ] } }}Araçlar
Henüz araç listesi yok. Uzak sunucular ilk sağlık kontrolünden sonra bir liste alır.
SSS
Bu sayfadan Cursor, Claude Desktop, Claude Code veya VS Code için stdio yapılandırmasını kopyalayın. PlugMCP proxy'sini değil, yayıncının kurulumunu kullanır.
Bu kayıtta gerekli bir ortam değişkeni listelenmiyor.
Eharness yerel olarak stdio üzerinden çalışır.
Son kontrol: 2026-10-28. Build your own agent harness on AI SDK v7: plugins, context, messages, streaming, sessions, compaction.