harnessrouterjs
v0.1.0
Published
JavaScript client and CLI for any server that speaks the Unified Harness Protocol: run agent tasks, stream progress, continue sessions, cancel work, collect artifacts.
Maintainers
Readme
harnessrouterjs
A JavaScript client and CLI for any server that speaks the Unified Harness Protocol.
A harness is a configured agent: a runtime, a model, instructions and limits. A task is one run of it — a real conversation against a real workspace, streamed back as it happens. This client sends work, follows it, continues the conversation, cancels it, and collects the files it produced.
It is a UHP client, not a client for one product. It works against the open-source HarnessRouter server, the hosted one, or any third implementation that passes the conformance suite.
npm install harnessrouterjsRun a task
import { HarnessRouter } from 'harnessrouterjs';
const client = new HarnessRouter({
baseUrl: 'http://localhost:3000',
apiKey: process.env.UHP_API_KEY,
});
const [harness] = await client.harnesses();
const response = await client.run('Summarise README.md in three bullets.', {
harnessId: harness.id,
});
response.status; // 'completed'
response.output; // ordered items: message, reasoning, function_call, function_call_outputFollow it while it happens
Agent tasks run for minutes. Without a stream a product can only show a spinner and hope.
for await (const event of client.stream('Add tests for the parser.', { harnessId })) {
if (event.type === 'response.output_text.delta') process.stdout.write(event.delta);
}Or let the client assemble it for you:
const task = await client.collect('Add tests for the parser.', { harnessId });
task.text; // the finished text
task.toolCalls; // what the agent ran
task.annotations; // the files it wrote
task.sequence; // { ok: false, missing: [12] } if the stream dropped an eventContinue, cancel, collect
const next = await client.continue(response.id, 'Now add tests for the function you just wrote.');
// same session, same working directory, same files
await client.cancel(next.id); // a request to stop; the session stays continuable
const files = await client.sessionFiles(next.metadata.session_id);
const body = await client.downloadFile(files[0].container_id, files[0].id);What it does for you
- Idempotency by default. Every
runcarries anIdempotency-Key, so a retry cannot start a second agent against the same files. - Retries only what is worth retrying.
session_busyand503s back off and try again;quota_exhaustedand every malformed request do not. APOST /v1/responsesthat may already have started is never resent on the client's own initiative. - Errors you can branch on.
UhpErrorcarries the server'stype,code,paramanddetail, plus aretryablegetter — no parsing prose to decide what to do. - Long timeouts. 15 minutes by default, because a 30-second HTTP timeout cancels healthy work.
CLI
export UHP_BASE_URL=http://localhost:3000
export UHP_API_KEY=…
npx harnessrouter discover # what the server is, and what it supports
npx harnessrouter harnesses # chrn_… Research agent [claude-code]
npx harnessrouter stream "Summarise README.md" --harness chrn_…API
| Method | What it does |
|---|---|
| discover() | The server's version, conformance class and capabilities (no token needed) |
| harnesses(), harness(id) | Configured harnesses in your scope |
| models(), harnessModels(id) | Model catalogues, with a computed available |
| run(input, opts) | Run a task and wait for the result |
| stream(input, opts) | Run a task and iterate its events |
| collect(input, opts) | Run, stream, and return the accumulated task |
| continue(responseId, input) | Continue the session that produced that response |
| getResponse(id), inputItems(id), wait(id) | Read a task back, or poll it to a terminal state |
| cancel(id), deleteResponse(id) | Stop work; delete history. Deliberately different things |
| sessions(), session(id), turns(id), sessionFiles(id) | Session listing and inspection |
| uploadFile(file, name), downloadFile(containerId, fileId) | Files in, artifacts out |
Specification
https://github.com/HarnessRouter/harnessrouter/tree/main/protocol
Apache-2.0.
