AnyBio

Govern SDK

The AnyBio AI governance SDK for JavaScript and TypeScript. Install @anybio/govern, watch the gate hold a synthetic message, then put it in front of your own model, with adapters for the Vercel AI SDK and LangChain.

@anybio/govern wraps the evaluate call for JavaScript and TypeScript, with adapters for the Vercel AI SDK and LangChain. It runs server-side only: the constructor throws if it finds a browser window, because the application key is a secret.

Pre-1.0. The package is 0.2.x. The public surface may change in a minor version, so pin it and read the changelog before upgrading.

Prerequisites

Watch it hold something

Before wiring anything into your own code, make the gate stop a message. To see it without installing anything, try the gate in your browser first. The CLI posts a synthetic draft that names a cure and a medication change, and prints what came back:

npm install @anybio/govern
npx @anybio/govern test-message

Success looks like a refusal. In enforce you see the hold inline. In observe the gate runs off the request path, so the CLI hands you a decision command to read the would-have a moment later. No patient data is involved, and the throwaway conversation gets a disclosure recorded first, so the turn is complete rather than a fragment.

npx @anybio/govern init reads your package.json, detects the Vercel AI SDK or LangChain, and prints the install line, the environment variable and the file to add. It writes nothing unless you pass --write, and it never edits a file you already have.

Gate your own model

guard is the short path. It runs your draft function, evaluates what it returns, and hands you what to deliver:

import { Govern } from "@anybio/govern";

const govern = new Govern({
  apiKey: process.env.ANYBIO_GOVERN_KEY,
  application: "care-companion",
});

const reply = await govern.guard(
  () => llm.chat(messages),
  { conversationId, turnId, channel: "sms", audience: "patient" },
);

if (reply.text !== "") await sendSms(patient.phone, reply.text);

That if is the whole contract. reply.text is the draft when the gate allows it. When it does not, text is either the holding line your clinical owner wrote in advance, or the empty string when nothing should be sent to this patient at all - because they opted out, consent is missing or revoked, or the frequency cap is reached. The empty string is the one "send nothing" value, so a caller that sends whatever it is handed sends nothing.

Call reply.ack() after you deliver. If you do not, the next turn in the same conversation reports the previous one for you.

reply.decisionId is the record. Everything else about the decision, including the reasons and the policy version that made it, is on the same object.

Framework adapters

The adapters are the install; the raw client is the escape hatch. Each ships as a subpath export and declares its framework as an optional peer dependency.

import { wrapLanguageModel, streamText } from "ai";
import { withGovern } from "@anybio/govern/vercel-ai";

const model = wrapLanguageModel({
  model: openai("gpt-4o"),
  middleware: withGovern({ govern, channel: "web_chat", audience: "patient" }),
});

const result = streamText({
  model,
  messages,
  providerOptions: {
    anybioGovern: { conversationId: chatId, turnId: messageId },
  },
});

A patient-audience stream is buffered and released whole after the decision, or replaced. A clinician-audience stream passes through and is evaluated once it ends. The decision lands in providerMetadata.anybioGovern.

After a hold

A hold waits for a person. Three ways to learn it resolved, in ascending order of how much you want to run:

  • waitForRelease(decisionId) polls and returns the released content, or null.
  • A webhook. handleWebhook verifies the signature and parses the event; WEBHOOK_EVENTS names what can arrive.
  • getDecision(decisionId) reads the current state whenever you ask.

If your first call fails

What you seeLikely causeFix
Constructor throws about windowThe client was constructed in browser codeIt is server-side only. Move it behind an API route
GovernConfigErrorA missing key, or a draft function returning something that is not textThe message names the field
GovernNotAvailableError in enforceThe API could not be reachedThis is a fail-closed hold, not a crash. reply.text carries the holding line when one is owed
Every decision comes back allowThe application's policy is in observeObserve records what would have happened. Check wouldHave
A replayed turnId returns the same decisionWorking as intendedturnId is the idempotency key

Reference

The package README is the exhaustive reference and ships with the code, so it never drifts from the version you installed: modes, channel rules, the holding line, consent and the frequency cap, hold resolution, disclosure, outcomes, streaming, and what the SDK does not cover.

On this page