SDKs

Official client libraries for Python, TypeScript, Java, LangChain/LangGraph, the OpenAI Agents SDK, CrewAI, Pydantic AI, and the Vercel AI SDK — plus an MCP server for Claude Code and Claude Desktop. Submit intents, retrieve decisions, handle errors — with full type safety.

At a Glance

Language Package Install Requirements
Python gaas-sdk pip install gaas-sdk Python 3.11+
TypeScript @governancehq/sdk npm install @governancehq/sdk Node.js 18+
Java com.gaas:gaas-sdk Maven / Gradle Java 17+
LangChain / LangGraph gaas-langchain pip install gaas-langchain Python 3.11+
OpenAI Agents SDK gaas-openai-agents pip install gaas-openai-agents Python 3.11+
MCP (Claude Code / Desktop) @governancehq/mcp npx -y @governancehq/mcp Node.js 18+
CrewAI gaas-crewai pip install gaas-crewai Python 3.11+
Vercel AI SDK @governancehq/vercel-ai npm install @governancehq/vercel-ai Node.js 18+, ai v7
Pydantic AI gaas-pydantic-ai pip install gaas-pydantic-ai Python 3.11+
All SDKs are Apache-2.0 licensed. Source and full documentation at github.com/H2OmAI/gaas under sdks/.

Python

Install

pip install gaas-sdk

Submit an Intent

from gaas_sdk import GaaSClient, build_intent, ActionType, TargetType

async with GaaSClient(
    "https://api.gaas.is",
    headers={"X-API-Key": "your_key"},
) as client:
    intent = build_intent(
        agent_id="billing_bot",
        action_type=ActionType.COMMUNICATE,
        verb="send_email",
        target_type=TargetType.PERSON,
        target_identifier="patient@example.com",
        summary="Send billing statement to patient",
        content={"recipient": "patient@example.com", "channel": "email"},
    )
    response = await client.submit_intent(intent)

    if response.data.verdict == "approve":
        send_email(response.data)
    elif response.data.verdict == "block":
        log(response.data.reasoning)

Sync Client

A synchronous client is available for non-async codebases:

from gaas_sdk import GaaSClientSync

with GaaSClientSync(
    "https://api.gaas.is",
    headers={"X-API-Key": "your_key"},
) as client:
    response = client.submit_intent(intent)

Bulk Submission

Submit up to 50 intents concurrently with partial failure support:

intents = [build_intent(...) for _ in range(10)]
response = await client.submit_intents_bulk(intents)

for result in response.data.results:
    if result.success:
        print(f"Decision: {result.decision.verdict}")
    else:
        print(f"Error: {result.error}")

Field Filtering

Request only specific fields to reduce response size:

response = await client.submit_intent(
    intent,
    fields="verdict,reasoning.summary,risk_assessment.overall_score"
)

Idempotency

Prevent duplicate submissions with idempotency keys (header-based recommended):

# Header-based (recommended)
response = await client.submit_intent(
    intent,
    headers={"Idempotency-Key": "unique-key-123"}
)

# Body-based (legacy)
intent = build_intent(
    idempotency_key="unique-key-123",
    agent_id="my-agent",
    # ...
)

Error Handling

from gaas_sdk import (
    GaaSError,
    GaaSValidationError,
    GaaSSemanticError,
    GaaSNotFoundError,
    GaaSConflictError,
    GaaSServerError,
    GaaSConnectionError,
)

try:
    response = await client.submit_intent(intent)
except GaaSValidationError as e:
    print(f"Validation failed (400): {e.message}")
except GaaSSemanticError as e:
    print(f"Semantic error (422): {e.message}")
except GaaSConflictError as e:
    print(f"Conflict (409): {e.message}")
except GaaSConnectionError:
    print("Could not reach GaaS server")
except GaaSError as e:
    # Catches all other GaaS errors (401, 402, 429, 500, etc.)
    print(f"GaaS error {e.code}: {e.message}")

Best Practices


TypeScript

Install

npm install @governancehq/sdk

Submit an Intent

import { GaaSClient, buildIntent, ActionType, TargetType } from '@governancehq/sdk';

const client = new GaaSClient({
  baseUrl: 'https://api.gaas.is',
  headers: { 'X-API-Key': 'your_key' },
});

const intent = buildIntent({
  agentId: 'billing_bot',
  actionType: ActionType.Communicate,
  verb: 'send_email',
  targetType: TargetType.Person,
  targetIdentifier: 'patient@example.com',
  summary: 'Send billing statement to patient',
  content: { recipient: 'patient@example.com', channel: 'email' },
});

const response = await client.submitIntent(intent);

if (response.data.verdict === 'approve') {
  sendEmail(response.data);
} else if (response.data.verdict === 'block') {
  console.log(response.data.verdictReason);
}
Automatic case conversion. The TypeScript SDK converts between camelCase (JavaScript) and snake_case (API) automatically. buildIntent({ agentId }) sends agent_id over the wire; response.data.riskAssessment comes from risk_assessment.

Error Handling

import { GaaSValidationError, GaaSConnectionError } from '@governancehq/sdk';

try {
  const response = await client.submitIntent(intent);
} catch (error) {
  if (error instanceof GaaSValidationError) {
    console.error(`Validation failed: ${error.message}`);
  } else if (error instanceof GaaSConnectionError) {
    console.error('Could not reach GaaS server');
  }
}

Java

Install

Maven:

<dependency>
    <groupId>com.gaas</groupId>
    <artifactId>gaas-sdk</artifactId>
    <version>0.2.0</version>
</dependency>

Gradle:

implementation 'com.gaas:gaas-sdk:0.2.0'

Submit an Intent

import com.gaas.sdk.*;

try (GaaSClient client = new GaaSClient("https://api.gaas.is", "your_key")) {
    IntentDeclaration intent = IntentBuilder.create()
        .agentId("billing_bot")
        .actionType(ActionType.COMMUNICATE)
        .verb("send_email")
        .targetType(TargetType.PERSON)
        .targetIdentifier("patient@example.com")
        .summary("Send billing statement to patient")
        .content(Map.of("recipient", "patient@example.com", "channel", "email"))
        .build();

    GaaSResponse<GovernanceDecision> response = client.submitIntent(intent);

    if (response.getData().getVerdict() == Verdict.APPROVE) {
        sendEmail(response.getData());
    } else if (response.getData().getVerdict() == Verdict.BLOCK) {
        System.out.println(response.getData().getReasoning());
    }
}

Async API

CompletableFuture<GaaSResponse<GovernanceDecision>> future =
    client.submitIntentAsync(intent);

Error Handling

try {
    GaaSResponse<GovernanceDecision> response = client.submitIntent(intent);
} catch (GaaSValidationException e) {
    System.out.println("Validation failed: " + e.getMessage());
} catch (GaaSConnectionException e) {
    System.out.println("Could not reach GaaS server");
}

LangChain / LangGraph

Install

pip install gaas-langchain

Optional extras: pip install gaas-langchain[langchain], gaas-langchain[langgraph], or gaas-langchain[all].

Configuration

from gaas_langchain import GaaSGovernanceConfig

config = GaaSGovernanceConfig(
    api_url="https://api.gaas.is",
    api_key="gsk_your_key",
    agent_id="my-langchain-agent",
    block_on_escalate=True,     # raise on ESCALATE verdicts too
    timeout_seconds=5.0,
    sensitivity="INTERNAL",
)

Govern a Single Tool

from gaas_langchain import govern_tool

safe_tool = govern_tool(my_tool, config=config)
# safe_tool.run() and safe_tool.arun() now submit governance
# intents before execution. Blocked actions raise GovernanceBlockedError.

Govern Multiple Tools

from gaas_langchain import govern_tools

safe_tools = govern_tools([tool_a, tool_b, tool_c], config=config)

LangGraph Node Decorator

from gaas_langchain import govern_node

@govern_node(config=config, node_name="send_email_node", financial_exposure_usd=0.0)
async def send_email(state):
    # Only executes if governance approves
    return {"status": "sent"}

Supports both sync and async node functions. Uses functools.wraps to preserve function metadata. Additional parameters: sensitivity, regulatory_domains.

Callback Handler (Observability)

from gaas_langchain import GaaSCallbackHandler

handler = GaaSCallbackHandler(config, enforce=False)
# Pass as a LangChain callback — logs all governance decisions
# Set enforce=True to raise GovernanceBlockedError on BLOCK/ESCALATE

# After agent run:
print(handler.summary())
# {"total_tool_calls": 5, "approved": 4, "blocked": 1, "block_rate": 0.2, ...}
handler.reset()  # clear log between runs

Error Handling

from gaas_langchain import GovernanceBlockedError

try:
    result = safe_tool.run("send payment")
except GovernanceBlockedError as e:
    print(e.verdict)              # "BLOCK" or "ESCALATE"
    print(e.decision_id)           # GaaS decision ID
    print(e.risk_score)             # 0.0–1.0
    print(e.blocking_policies)      # ["pol_t1_002", ...]
    print(e.governance_proof_token)  # ECDSA-signed proof token ID

OpenAI Agents SDK

Install

pip install gaas-openai-agents[openai-agents]

Govern Function Tools

from agents import Agent, Runner, function_tool
from gaas_openai_agents import govern_tools, GaaSGovernanceConfig

config = GaaSGovernanceConfig(
    api_url="https://api.gaas.is",
    api_key="gsk_your_key",
    agent_id="my-agent",
)

agent = Agent(
    name="assistant",
    tools=govern_tools([search_tool, email_tool], config=config),
)
result = await Runner.run(agent, "...")

Every tool call submits a governance intent before executing. Tool metadata (name, description, schema, guardrails) is preserved. Only FunctionTool instances can be governed — hosted tools (web search, code interpreter) execute on OpenAI's side.

BLOCK Halts the Run

from gaas_openai_agents import GovernanceBlockedError

try:
    result = await Runner.run(agent, "wire $250k to the new vendor")
except GovernanceBlockedError as e:
    print(e.verdict)               # "BLOCK", "ESCALATE", "ESCALATE_DENY", ...
    print(e.decision_id)            # GaaS decision ID
    print(e.blocking_policies)      # ["pol_t1_002", ...]
    print(e.governance_proof_token)  # ECDSA-signed proof token ID

GovernanceBlockedError subclasses the framework's AgentsException, so it propagates out of Runner.run() unchanged — a blocked action halts the run instead of becoming error text the model can route around. Errors inside your own tool functions keep the framework's default behaviour (formatted for the model, run continues).

Hold-and-Poll on ESCALATE

config = GaaSGovernanceConfig(
    api_key="gsk_your_key",
    agent_id="my-agent",
    hold_on_escalate=True,          # wait for the human decision
    escalation_poll_seconds=5.0,
    escalation_max_wait_seconds=600.0,
)

On ESCALATE the tool call holds while GaaS routes the escalation to a human reviewer. Approve/modify lets the tool execute; deny or timeout raises GovernanceBlockedError with verdict ESCALATE_DENY or ESCALATE_TIMEOUT.


CrewAI

Install

pip install gaas-crewai[crewai]

Govern Crew Tools

from crewai import Agent
from gaas_crewai import govern_tools, GaaSGovernanceConfig

config = GaaSGovernanceConfig(
    api_url="https://api.gaas.is",
    api_key="gsk_your_key",
    agent_id="my-crew-agent",
)

researcher = Agent(
    role="researcher",
    goal="...",
    backstory="...",
    tools=govern_tools([search_tool, email_tool], config=config),
)

Works with hand-written BaseTool subclasses and @tool-decorated functions alike. Name, description, and args_schema are preserved — the agent sees an identical tool.

Verdict Semantics

On BLOCK the wrapped tool never executes and GovernanceBlockedError is raised from the tool. Inside a running Crew, the framework's tool-usage loop surfaces the block to the agent as an error observation and may retry — each retry submits a fresh intent that is re-blocked, and every attempt lands on the signed audit chain. Hold-and-poll on ESCALATE is supported via hold_on_escalate=True (approve/modify proceeds; deny/timeout raises ESCALATE_DENY / ESCALATE_TIMEOUT).

Error Handling

from gaas_crewai import GovernanceBlockedError

try:
    result = governed_tool.run(to="all-customers@list", subject="...")
except GovernanceBlockedError as e:
    print(e.verdict)               # "BLOCK", "ESCALATE", "ESCALATE_DENY", ...
    print(e.decision_id)            # GaaS decision ID
    print(e.blocking_policies)      # ["pol_t1_002", ...]
    print(e.governance_proof_token)  # ECDSA-signed proof token ID

Pydantic AI

Install

pip install gaas-pydantic-ai[pydantic-ai]

Govern a Toolset

from pydantic_ai import Agent
from gaas_pydantic_ai import govern_tools, GaaSGovernanceConfig

config = GaaSGovernanceConfig(
    api_url="https://api.gaas.is",
    api_key="gsk_your_key",
    agent_id="my-agent",
)

agent = Agent(
    "anthropic:claude-sonnet-5",
    toolsets=[govern_tools([search_web, send_email], config=config)],
)
result = await agent.run("...")

Wrap an existing toolset with govern_toolset(toolset, config=config). One wrapper governs every tool in the set; tool names and parsed arguments flow into the governance intent.

BLOCK Halts the Run — Natively

from gaas_pydantic_ai import GovernanceBlockedError

try:
    result = await agent.run("wire $250k to the new vendor")
except GovernanceBlockedError as e:
    print(e.verdict)               # "BLOCK", "ESCALATE", "ESCALATE_DENY", ...
    print(e.decision_id)            # GaaS decision ID
    print(e.blocking_policies)      # ["pol_t1_002", ...]
    print(e.governance_proof_token)  # ECDSA-signed proof token ID

Pydantic AI propagates ordinary exceptions out of agent.run() by design, so a blocked action halts the run without any framework workarounds. The adapter deliberately never uses ModelRetry — the model cannot route around a block. Hold-and-poll on ESCALATE is supported via hold_on_escalate=True (approve/modify proceeds; deny/timeout raises ESCALATE_DENY / ESCALATE_TIMEOUT).


Vercel AI SDK

Install

npm install @governancehq/vercel-ai

ai v7 is a peer dependency.

Govern a ToolSet

import { generateText, stepCountIs } from "ai";
import { governTools, stopOnGovernanceBlock } from "@governancehq/vercel-ai";

const config = { apiKey: "gsk_your_key", agentId: "my-agent" };

const result = await generateText({
  model,
  tools: governTools({ sendEmail, searchWeb }, config),
  stopWhen: [stepCountIs(5), stopOnGovernanceBlock()],
  prompt: "...",
});

Every governed tool call submits a governance intent before executing. Tool metadata (description, inputSchema) is preserved; the ToolSet key becomes the tool name on the intent. Provider-executed tools (no execute) pass through unchanged.

Verdict Semantics

On BLOCK the tool never executes and GovernanceBlockedError is thrown from execute. The AI SDK surfaces tool errors to the model as tool-error parts and the generation continues — each retry is re-governed and lands on the signed audit chain. Add stopOnGovernanceBlock() to stopWhen to halt the agent loop at the blocked step instead; inspect the final step's tool-error part for the decision ID and blocking policies. Hold-and-poll on ESCALATE is supported via holdOnEscalate: true (approve/modify proceeds; deny/timeout throws ESCALATE_DENY / ESCALATE_TIMEOUT).


MCP Server (Claude Code / Claude Desktop)

@governancehq/mcp is a Model Context Protocol server that gives Claude Code, Claude Desktop, and any MCP client direct access to the GaaS governance pipeline.

Setup — Claude Code

claude mcp add gaas --env GAAS_API_KEY=gsk_your_key -- npx -y @governancehq/mcp

Setup — Claude Desktop / .mcp.json

{
  "mcpServers": {
    "gaas": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@governancehq/mcp"],
      "env": {
        "GAAS_API_URL": "https://api.gaas.is",
        "GAAS_API_KEY": "gsk_your_key"
      }
    }
  }
}

Environment: GAAS_API_KEY (required), GAAS_API_URL (default https://api.gaas.is), GAAS_TIMEOUT_MS (default 30000).

Tools

Tool What it does
gaas_submit_intent Submit a governance intent and receive the decision (APPROVE / ESCALATE / BLOCK) with risk assessment and signed proof token. mode=shadow evaluates without enforcement.
gaas_check_decision Fetch the decision for an intent ID — poll after an ESCALATE verdict.
gaas_query_audit Fetch the full signed audit record: pipeline stages, policy evaluations, hash-chain position.
gaas_validate_governance_files Dry-run validation of .gaas/ governance files against the authmd spec. Never mutates anything.

Resources: governance://current (applied bundle + drift), governance://policy-packs, governance://spec. No write-side policy tools — applying governance bundles from an LLM session is the wrong trust direction.

HTTP Mode

GAAS_API_KEY=gsk_... npx -y @governancehq/mcp --http --port 3917
# MCP endpoint: POST http://localhost:3917/mcp

Common Patterns

Builder Pattern

All three SDKs provide a builder function (build_intent in Python, buildIntent in TypeScript, IntentBuilder in Java) that flattens the nested intent model into a flat argument list. This handles the agent.id, action.type, action.target.identifier nesting so you don't have to construct nested objects manually.

Response Structure

Every SDK method returns a typed response with two fields:

Retrieving Decisions

Python:

decision = await client.get_decision("intent-id")

TypeScript:

const decision = await client.getDecision('intent-id');

Java:

GaaSResponse<GovernanceDecision> decision = client.getDecision("intent-id");

Related Pages