Harness Client SDK
Packages
harness-sdk— Core client. Framework-agnostic.@harness-sdk/react— React bindings.@assistant-ui/react-harness-sdk— assistant-ui integration (see assistant-ui.md).
Architecture
useHarness({ transport }) // React hook
└─ useResource(HarnessClient({ transport }))
└─ tapResource(transport) // mounts the transport resourceThe user provides a transport. HarnessClient is a tap resource that mounts it and exposes the API. useHarness is a convenience hook that wraps useResource(HarnessClient(...)).
HarnessTransport
A HarnessTransport is a tap resource that manages a connection to the server. It must return:
interface HarnessTransportAPI {
state: HarnessState;
send(command: HarnessCommand): void;
}HarnessWebsocketTransport
The built-in transport. A resource() that opens a WebSocket, applies incoming deltas, and exposes the current state reactively.
const HarnessWebsocketTransport = resource(
({ url }: { url: string }) => {
const [state, setState] = tapState<HarnessState>({
status: "idle",
messages: [],
});
const onMessage = tapEffectEvent((event: MessageEvent) => {
const msg = JSON.parse(event.data);
if (msg.type === "state") {
setState(msg.state);
} else if (msg.type === "delta") {
setState((prev) => applyOperations(prev, msg.operations));
}
});
const wsRef = tapRef<WebSocket | null>(null);
tapEffect(() => {
const ws = new WebSocket(url);
wsRef.current = ws;
ws.onmessage = onMessage;
return () => ws.close();
}, [url]);
return {
state,
send: (command: HarnessCommand) => {
wsRef.current?.send(
JSON.stringify({ type: "commands", commands: [command] }),
);
},
};
},
);HarnessClient
HarnessClient is a tap resource that mounts a transport and exposes the harness API.
const HarnessClient = resource(
({ transport }: { transport: ResourceElement<HarnessTransportAPI> }) => {
const t = tapResource(transport);
return {
state: t.state,
send: t.send,
};
},
);The transport is mounted via tapResource, giving it lifecycle management — it connects when the client mounts and disconnects when it unmounts. Because tap manages identity, the same transport config produces the same instance across re-renders.
Vanilla Usage
import { Harness, HarnessWebsocketTransport } from "harness-sdk";
const harness = new Harness({
transport: HarnessWebsocketTransport({ url: "ws://localhost:8787/ws" }),
});
harness.state; // current HarnessState
harness.send({ type: "submit", prompt: "Hello" });
harness.subscribe(() => {
console.log(harness.state);
});
// cleanup
harness.dispose();new Harness() internally calls createResourceRoot() and renders HarnessClient into it, exposing state, send, subscribe, and dispose.
React Usage
useHarness
import { useHarness } from "@harness-sdk/react";
import { HarnessWebsocketTransport } from "harness-sdk";
function App() {
const harness = useHarness({
transport: HarnessWebsocketTransport({ url: "ws://localhost:8787/ws" }),
});
harness.send({ type: "submit", prompt: "Hello" });
return <div>{harness.state.status}</div>;
}useHarness is useResource(HarnessClient({ transport })). Passing HarnessWebsocketTransport({ url }) inline is safe — tap preserves the resource instance as long as url hasn't changed.
assistant-ui Integration
See assistant-ui.md. Out of scope for the core SDK.