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+ |
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
- Rate Limiting: Implement exponential backoff for 429 errors. Default limit: 100 requests/minute per organization.
- Quota Monitoring: Check
X-GaaS-Quota-Remainingresponse header to track quota usage proactively. - Idempotency: Always use idempotency keys for critical actions to prevent duplicate processing on retry.
- Error Retry: Retry 5xx server errors with exponential backoff. Do not retry 4xx client errors (except 429).
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);
}
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:
data— the response payload (e.g.,GovernanceDecision,AuditRecord,HealthStatus)meta— request metadata: request ID, decision ID, pipeline latency, status code
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
- Getting Started — full quickstart walkthrough
- Intent Declaration API — endpoint reference and schema details
- Shadow Mode — test governance without enforcement