Harness SDK

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 resource

The 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.