Fulcrum TypeScript SDK Reference
Comprehensive Guide to AI Governance for TypeScript/Node.js Applications
Version: 0.1.3
Node.js Requirement: >=18.0.0
Package: @fulcrum-governance/sdk
MCP Name: io.github.Fulcrum-Governance/fulcrum
Table of Contents
- Installation
- Configuration
- TypeScript Types
- Core Classes
- FulcrumClient
- Envelope
- Methods Reference
- Error Handling
- Browser Support
- Framework Integrations
- Vercel AI SDK
- LangChain.js
- Express Middleware
- Next.js API Routes
- Fastify
- Best Practices
- Troubleshooting
Installation
npm
yarn
pnpm
Dependencies
The SDK includes the following dependencies:
| Package | Version | Purpose |
|---|---|---|
@grpc/grpc-js |
^1.9.0 | gRPC communication |
@grpc/proto-loader |
^0.7.0 | Protocol buffer loading |
uuid |
^9.0.0 | Unique identifier generation |
Configuration
Environment Variables
The SDK can be configured via environment variables for secure, deployment-friendly configuration:
# Required
export FULCRUM_HOST="localhost:50051" # gRPC server address
export FULCRUM_API_KEY="your-api-key" # API authentication key
# Optional
export FULCRUM_TENANT_ID="your-tenant-id" # Default tenant identifier
export FULCRUM_TIMEOUT_MS="500" # Request timeout (milliseconds)
export FULCRUM_ON_FAILURE="FAIL_OPEN" # Failure mode: FAIL_OPEN | FAIL_CLOSED
export FULCRUM_ENABLE_TLS="true" # Enable TLS encryption
export FULCRUM_CA_CERT_PATH="/path/to/ca.crt" # Custom CA certificate
ClientOptions Interface
import { FulcrumClient } from '@fulcrum-governance/sdk';
interface FulcrumClientOptions {
/**
* gRPC server address in host:port format
* @example "localhost:50051"
* @example "fulcrum.example.com:443"
*/
host: string;
/**
* API key for authentication
* Sent as x-api-key header in gRPC metadata
*/
apiKey?: string;
/**
* Behavior when Fulcrum server is unreachable or returns errors
* - FAIL_OPEN: Allow execution to proceed (default)
* - FAIL_CLOSED: Block execution on any error
*/
onFailure?: 'FAIL_OPEN' | 'FAIL_CLOSED';
/**
* Request timeout in milliseconds
* @default 500
*/
timeoutMs?: number;
/**
* Default tenant ID for all operations
* Can be overridden per-envelope
*/
tenantId?: string;
/**
* Enable TLS encryption for gRPC connection
* @default false for localhost, true otherwise
*/
enableTls?: boolean;
/**
* Path to custom CA certificate for TLS verification
* Use for self-signed certificates or private CAs
*/
caCertPath?: string;
}
Configuration Examples
Minimal Configuration:
Full Configuration:
import { FulcrumClient } from '@fulcrum-governance/sdk';
const client = new FulcrumClient({
host: 'fulcrum.production.example.com:443',
apiKey: process.env.FULCRUM_API_KEY,
tenantId: 'tenant-abc123',
onFailure: 'FAIL_CLOSED',
timeoutMs: 1000,
enableTls: true,
caCertPath: '/etc/ssl/certs/fulcrum-ca.crt'
});
Environment-Based Configuration:
// The SDK supports auto-discovery from environment variables
const client = FulcrumClient.fromEnv();
TypeScript Types
Core Interfaces
FulcrumClientOptions
export interface FulcrumClientOptions {
host: string;
apiKey?: string;
onFailure?: 'FAIL_OPEN' | 'FAIL_CLOSED';
timeoutMs?: number;
tenantId?: string;
enableTls?: boolean;
caCertPath?: string;
}
EnvelopeOptions
export interface EnvelopeOptions {
/**
* Identifier for the workflow or agent being governed
* @required
*/
workflowId: string;
/**
* Unique identifier for this specific execution
* Auto-generated UUID if not provided
*/
executionId?: string;
/**
* Tenant identifier for multi-tenant isolation
* Defaults to client's tenantId or 'default-tenant'
*/
tenantId?: string;
/**
* Type of adapter/framework being used
* @default "typescript-sdk"
* @example "langchain", "vercel-ai", "custom"
*/
adapterType?: string;
/**
* Additional metadata for policy evaluation and audit
* Key-value pairs passed to policy engine
*/
metadata?: Record<string, string>;
}
GuardResult
export interface GuardResult {
/**
* Whether the action is allowed
*/
allowed: boolean;
/**
* Decision type from policy engine
* @example "ALLOW", "DENY", "EVALUATION_DECISION_ALLOW"
*/
decision: string;
/**
* Human-readable reason for the decision
*/
reason: string;
}
Policy Types
interface PolicyDecision {
allowed: boolean;
policyId?: string;
decision: 'ALLOW' | 'DENY' | 'WARN' | 'REQUIRE_APPROVAL';
matchedRules: RuleMatch[];
actions: PolicyAction[];
message?: string;
}
interface RuleMatch {
ruleId: string;
ruleName: string;
matchedConditions: string[];
priority: number;
}
interface PolicyAction {
actionType: ActionType;
parameters: Record<string, string>;
message?: string;
severity: SeverityLevel;
terminal: boolean;
}
Cost Types
interface CostSnapshot {
/**
* Total cost in USD
*/
totalUsd: number;
/**
* Total input tokens consumed
*/
inputTokens: number;
/**
* Total output tokens generated
*/
outputTokens: number;
/**
* Number of LLM API calls made
*/
llmCalls: number;
/**
* Number of tool/function calls made
*/
toolCalls: number;
/**
* Cost breakdown by model
*/
modelBreakdown: Record<string, ModelCostEntry>;
}
interface ModelCostEntry {
modelId: string;
calls: number;
inputTokens: number;
outputTokens: number;
cost: number;
}
Checkpoint Types
interface Checkpoint {
/**
* Unique checkpoint identifier
*/
checkpointId: string;
/**
* Envelope this checkpoint belongs to
*/
envelopeId: string;
/**
* Checkpoint version number
*/
version: number;
/**
* Hash of state data for integrity verification
*/
stateHash: string;
/**
* When the checkpoint was created
*/
createdAt: Date;
}
Enums
FailureMode
export enum FailureMode {
/**
* Allow execution when governance service is unavailable
* Use for non-critical applications
*/
FAIL_OPEN = 'FAIL_OPEN',
/**
* Block execution when governance service is unavailable
* Use for high-security applications
*/
FAIL_CLOSED = 'FAIL_CLOSED'
}
EvaluationDecision
enum EvaluationDecision {
EVALUATION_DECISION_UNSPECIFIED = 'EVALUATION_DECISION_UNSPECIFIED',
EVALUATION_DECISION_ALLOW = 'EVALUATION_DECISION_ALLOW',
EVALUATION_DECISION_DENY = 'EVALUATION_DECISION_DENY',
EVALUATION_DECISION_WARN = 'EVALUATION_DECISION_WARN',
EVALUATION_DECISION_REQUIRE_APPROVAL = 'EVALUATION_DECISION_REQUIRE_APPROVAL'
}
EnvelopeStatus
enum EnvelopeStatus {
ENVELOPE_STATUS_UNSPECIFIED = 'ENVELOPE_STATUS_UNSPECIFIED',
ENVELOPE_STATUS_PENDING = 'ENVELOPE_STATUS_PENDING',
ENVELOPE_STATUS_AUTHORIZED = 'ENVELOPE_STATUS_AUTHORIZED',
ENVELOPE_STATUS_RUNNING = 'ENVELOPE_STATUS_RUNNING',
ENVELOPE_STATUS_PAUSED = 'ENVELOPE_STATUS_PAUSED',
ENVELOPE_STATUS_COMPLETED = 'ENVELOPE_STATUS_COMPLETED',
ENVELOPE_STATUS_FAILED = 'ENVELOPE_STATUS_FAILED',
ENVELOPE_STATUS_TERMINATED = 'ENVELOPE_STATUS_TERMINATED',
ENVELOPE_STATUS_BUDGET_EXCEEDED = 'ENVELOPE_STATUS_BUDGET_EXCEEDED',
ENVELOPE_STATUS_POLICY_VIOLATION = 'ENVELOPE_STATUS_POLICY_VIOLATION',
ENVELOPE_STATUS_TIMEOUT = 'ENVELOPE_STATUS_TIMEOUT'
}
ActionType
enum ActionType {
ACTION_TYPE_ALLOW = 'ACTION_TYPE_ALLOW',
ACTION_TYPE_DENY = 'ACTION_TYPE_DENY',
ACTION_TYPE_WARN = 'ACTION_TYPE_WARN',
ACTION_TYPE_MODIFY = 'ACTION_TYPE_MODIFY',
ACTION_TYPE_REDIRECT = 'ACTION_TYPE_REDIRECT',
ACTION_TYPE_AUDIT = 'ACTION_TYPE_AUDIT',
ACTION_TYPE_THROTTLE = 'ACTION_TYPE_THROTTLE',
ACTION_TYPE_REQUIRE_APPROVAL = 'ACTION_TYPE_REQUIRE_APPROVAL',
ACTION_TYPE_NOTIFY = 'ACTION_TYPE_NOTIFY'
}
SeverityLevel
enum SeverityLevel {
SEVERITY_LEVEL_INFO = 'SEVERITY_LEVEL_INFO',
SEVERITY_LEVEL_LOW = 'SEVERITY_LEVEL_LOW',
SEVERITY_LEVEL_MEDIUM = 'SEVERITY_LEVEL_MEDIUM',
SEVERITY_LEVEL_HIGH = 'SEVERITY_LEVEL_HIGH',
SEVERITY_LEVEL_CRITICAL = 'SEVERITY_LEVEL_CRITICAL'
}
Core Classes
FulcrumClient
The FulcrumClient is the main entry point for interacting with Fulcrum's governance platform.
Constructor
Static Methods
class FulcrumClient {
/**
* Create a client from environment variables
* @returns FulcrumClient configured from FULCRUM_* env vars
*/
static fromEnv(): FulcrumClient;
}
Instance Methods
| Method | Signature | Description |
|---|---|---|
envelope |
(options: EnvelopeOptions) => Promise<Envelope> |
Create a governance envelope |
evaluatePolicy |
(context: EvaluationContext) => Promise<GuardResult> |
Evaluate policies directly |
createEnvelopeGrpc |
(tenantId: string, adapterType?: string, metadata?: Record<string, string>) => Promise<string \| null> |
Create envelope via gRPC |
shutdown |
() => void |
Clean up resources |
Complete Example
import { FulcrumClient } from '@fulcrum-governance/sdk';
// Initialize client
const client = new FulcrumClient({
host: 'localhost:50051',
apiKey: 'your-api-key',
onFailure: 'FAIL_OPEN',
timeoutMs: 500
});
// Create an envelope for an AI workflow
const envelope = await client.envelope({
workflowId: 'customer-support-bot',
tenantId: 'tenant-123',
metadata: {
user_id: 'user-456',
session_id: 'session-789'
}
});
// Use the envelope for governance checks
const allowed = await envelope.guard('send_email', 'Email content here');
if (allowed) {
// Proceed with action
await sendEmail(content);
envelope.log('email_sent', { recipient: 'user@example.com' });
} else {
envelope.log('action_blocked', { action: 'send_email' });
}
// Clean up when done
client.shutdown();
Envelope
The Envelope class represents a governance context for a single AI execution.
Constructor
class Envelope {
constructor(
client: FulcrumClient,
options: Required<EnvelopeOptions>,
envelopeId: string
);
}
Instance Properties
| Property | Type | Description |
|---|---|---|
envelopeId |
string |
Unique identifier for this envelope |
executionId |
string |
Execution identifier |
tenantId |
string |
Tenant identifier |
workflowId |
string |
Workflow identifier |
Instance Methods
| Method | Signature | Description |
|---|---|---|
guard |
(actionName: string, inputText?: string, metadata?: Record<string, string>) => Promise<boolean> |
Check if action is allowed by policy |
log |
(eventType: string, payload?: Record<string, any>) => void |
Log audit event |
checkpoint |
() => Promise<Checkpoint> |
Create execution checkpoint |
getCost |
() => Promise<CostSnapshot> |
Get current execution cost |
complete |
(result?: any) => Promise<void> |
Mark envelope as complete |
Methods Reference
client.envelope()
Creates a new governance envelope for an AI execution.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
options.workflowId |
string |
Yes | Identifier for the workflow |
options.executionId |
string |
No | Custom execution ID |
options.tenantId |
string |
No | Tenant identifier |
options.adapterType |
string |
No | Framework adapter type |
options.metadata |
Record<string, string> |
No | Additional metadata |
Returns: Promise<Envelope>
Example:
const envelope = await client.envelope({
workflowId: 'content-moderation',
tenantId: 'tenant-abc',
adapterType: 'custom',
metadata: {
user_role: 'admin',
department: 'support'
}
});
client.evaluatePolicy()
Directly evaluate policies without creating an envelope.
async evaluatePolicy(context: {
tenantId: string;
workflowId: string;
inputText?: string;
attributes?: Record<string, string>;
}): Promise<GuardResult>
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
tenantId |
string |
Yes | Tenant identifier |
workflowId |
string |
Yes | Workflow identifier |
inputText |
string |
No | Text content to evaluate |
attributes |
Record<string, string> |
No | Additional attributes |
Returns: Promise<GuardResult>
Example:
const result = await client.evaluatePolicy({
tenantId: 'tenant-123',
workflowId: 'chat-agent',
inputText: 'User input to check',
attributes: {
action: 'generate_response',
model: 'gpt-4'
}
});
console.log(`Decision: ${result.decision}`);
console.log(`Allowed: ${result.allowed}`);
console.log(`Reason: ${result.reason}`);
envelope.guard()
Check if an action is allowed by the policy engine.
async guard(
actionName: string,
inputText?: string,
metadata?: Record<string, string>
): Promise<boolean>
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
actionName |
string |
Yes | Name of the action to check |
inputText |
string |
No | Input text for content analysis |
metadata |
Record<string, string> |
No | Additional context |
Returns: Promise<boolean> - true if allowed, false if denied
Example:
const envelope = await client.envelope({ workflowId: 'agent-workflow' });
// Simple action check
const canSendEmail = await envelope.guard('send_email');
// With content analysis
const canPost = await envelope.guard(
'post_to_social_media',
'Tweet content here',
{ platform: 'twitter' }
);
// With multiple metadata fields
const canExecuteTool = await envelope.guard(
'execute_tool',
JSON.stringify({ command: 'ls', args: ['-la'] }),
{
tool_name: 'shell',
risk_level: 'high',
requires_approval: 'true'
}
);
envelope.log()
Log an audit event for the execution.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
eventType |
string |
Yes | Type of event to log |
payload |
Record<string, any> |
No | Event data |
Example:
// Log successful action
envelope.log('email_sent', {
recipient: 'user@example.com',
subject: 'Welcome',
timestamp: new Date().toISOString()
});
// Log blocked action
envelope.log('action_blocked', {
action: 'send_email',
reason: 'policy_violation',
policy_id: 'policy-123'
});
// Log LLM call
envelope.log('llm_call', {
model: 'gpt-4',
input_tokens: 150,
output_tokens: 200,
latency_ms: 1250
});
envelope.getCost()
Get the current cost summary for the envelope execution.
Returns: Promise<CostSnapshot>
Example:
const cost = await envelope.getCost();
console.log(`Total cost: $${cost.totalUsd.toFixed(4)}`);
console.log(`Input tokens: ${cost.inputTokens}`);
console.log(`Output tokens: ${cost.outputTokens}`);
console.log(`LLM calls: ${cost.llmCalls}`);
console.log(`Tool calls: ${cost.toolCalls}`);
// Model breakdown
for (const [modelId, stats] of Object.entries(cost.modelBreakdown)) {
console.log(` ${modelId}:`);
console.log(` Calls: ${stats.calls}`);
console.log(` Cost: $${stats.cost.toFixed(4)}`);
}
envelope.checkpoint()
Create a checkpoint for execution state recovery.
Returns: Promise<Checkpoint>
Example:
// Before long-running operation
const checkpoint = await envelope.checkpoint();
console.log(`Checkpoint created: ${checkpoint.checkpointId}`);
try {
await longRunningOperation();
} catch (error) {
// Can recover from checkpoint
console.log(`Failed after checkpoint ${checkpoint.checkpointId}`);
}
envelope.complete()
Mark the envelope as completed.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
result |
any |
No | Final result of the execution |
Example:
try {
// ... execution logic ...
await envelope.complete({
status: 'success',
output: 'Task completed successfully',
metrics: {
duration_ms: 1500,
actions_taken: 3
}
});
} catch (error) {
await envelope.complete({
status: 'error',
error: error.message
});
}
Error Handling
Error Classes
The SDK provides specific error classes for different failure scenarios:
import {
FulcrumError, // Base error class
PolicyViolationError, // Action blocked by policy
BudgetExceededError, // Budget limit reached
ConnectionError, // Server unreachable
AuthenticationError, // Invalid API key
TimeoutError, // Request timed out
} from '@fulcrum-governance/sdk/errors';
Error Properties
FulcrumError (Base Class)
class FulcrumError extends Error {
code: string; // Error code (e.g., "POLICY_VIOLATION")
details?: any; // Additional error details
timestamp: Date; // When the error occurred
}
PolicyViolationError
class PolicyViolationError extends FulcrumError {
policyId: string; // ID of the violated policy
matchedRules: string[]; // Rules that triggered the violation
severity: SeverityLevel; // Severity of the violation
}
BudgetExceededError
class BudgetExceededError extends FulcrumError {
budgetId: string; // ID of the exceeded budget
currentSpend: number; // Current spend in USD
budgetLimit: number; // Budget limit in USD
limitType: string; // Type of limit exceeded (cost, tokens, calls)
}
Error Handling Examples
Comprehensive Error Handling:
import { FulcrumClient } from '@fulcrum-governance/sdk';
import {
FulcrumError,
PolicyViolationError,
BudgetExceededError,
ConnectionError,
AuthenticationError,
TimeoutError
} from '@fulcrum-governance/sdk/errors';
const client = new FulcrumClient({
host: 'localhost:50051',
apiKey: 'your-api-key',
onFailure: 'FAIL_CLOSED' // Strict mode
});
async function executeWithGovernance(userInput: string) {
try {
const envelope = await client.envelope({ workflowId: 'my-agent' });
const allowed = await envelope.guard('process_input', userInput);
if (!allowed) {
console.log('Action not allowed by policy');
return { success: false, reason: 'policy_denied' };
}
// Proceed with execution
const result = await processInput(userInput);
await envelope.complete({ result });
return { success: true, result };
} catch (error) {
if (error instanceof PolicyViolationError) {
console.error(`Policy violation detected:`);
console.error(` Policy ID: ${error.policyId}`);
console.error(` Matched rules: ${error.matchedRules.join(', ')}`);
console.error(` Severity: ${error.severity}`);
// Log for audit
await logSecurityEvent({
type: 'policy_violation',
policyId: error.policyId,
input: userInput.substring(0, 100)
});
return { success: false, reason: 'policy_violation', details: error };
} else if (error instanceof BudgetExceededError) {
console.error(`Budget exceeded:`);
console.error(` Budget ID: ${error.budgetId}`);
console.error(` Current spend: $${error.currentSpend.toFixed(2)}`);
console.error(` Budget limit: $${error.budgetLimit.toFixed(2)}`);
// Notify billing team
await notifyBillingTeam(error);
return { success: false, reason: 'budget_exceeded', details: error };
} else if (error instanceof ConnectionError) {
console.error(`Cannot reach Fulcrum server: ${error.message}`);
// In FAIL_CLOSED mode, we reject the request
return { success: false, reason: 'service_unavailable' };
} else if (error instanceof AuthenticationError) {
console.error(`Authentication failed: ${error.message}`);
// Alert ops team about auth issues
await alertOpsTeam('fulcrum_auth_failure', error);
return { success: false, reason: 'authentication_failed' };
} else if (error instanceof TimeoutError) {
console.error(`Request timed out after ${error.timeoutMs}ms`);
// Could retry with exponential backoff
return { success: false, reason: 'timeout' };
} else {
// Unknown error
console.error('Unexpected error:', error);
throw error;
}
}
}
Fail-Open vs Fail-Closed:
// FAIL_OPEN: Allow execution when Fulcrum is unavailable (default)
const clientOpen = new FulcrumClient({
host: 'localhost:50051',
onFailure: 'FAIL_OPEN'
});
// With FAIL_OPEN, connection errors result in allowed=true
// This is suitable for non-critical applications
// FAIL_CLOSED: Block execution when Fulcrum is unavailable
const clientClosed = new FulcrumClient({
host: 'localhost:50051',
onFailure: 'FAIL_CLOSED'
});
// With FAIL_CLOSED, connection errors result in allowed=false
// This is suitable for high-security applications
Error Codes
| Code | Description |
|---|---|
POLICY_VIOLATION |
Action blocked by policy rule |
BUDGET_EXCEEDED |
Cost or token budget exceeded |
AUTHENTICATION_FAILED |
Invalid or missing API key |
CONNECTION_ERROR |
Cannot reach Fulcrum server |
TIMEOUT |
Request exceeded timeout limit |
INVALID_REQUEST |
Malformed request parameters |
ENVELOPE_NOT_FOUND |
Referenced envelope does not exist |
TENANT_NOT_FOUND |
Referenced tenant does not exist |
INTERNAL_ERROR |
Server-side error |
Browser Support
For browser environments where gRPC is not available, use the REST client:
Installation
The REST client is included in the main package:
Configuration
interface FulcrumRestClientOptions {
/**
* Base URL for Fulcrum REST API
* @example "https://api.fulcrum.dev"
*/
baseUrl: string;
/**
* API key for authentication
*/
apiKey: string;
/**
* Default tenant ID
*/
tenantId?: string;
/**
* Request timeout in milliseconds
* @default 5000
*/
timeoutMs?: number;
/**
* Failure mode
* @default 'FAIL_OPEN'
*/
onFailure?: 'FAIL_OPEN' | 'FAIL_CLOSED';
}
Example Usage
import { FulcrumRestClient } from '@fulcrum-governance/sdk/rest';
// Initialize REST client for browser
const client = new FulcrumRestClient({
baseUrl: 'https://api.fulcrum.dev',
apiKey: 'your-api-key',
tenantId: 'tenant-123'
});
// Same API as gRPC client
const envelope = await client.envelope({
workflowId: 'browser-chat-widget',
metadata: {
user_agent: navigator.userAgent,
page_url: window.location.href
}
});
// Check if message is allowed
const allowed = await envelope.guard('send_message', userMessage);
if (allowed) {
// Send message to backend
await sendMessage(userMessage);
envelope.log('message_sent');
}
Browser Bundle
For direct browser usage without a bundler:
<script src="https://unpkg.com/@fulcrum-governance/sdk/dist/browser.min.js"></script>
<script>
const client = new Fulcrum.RestClient({
baseUrl: 'https://api.fulcrum.dev',
apiKey: 'your-api-key'
});
// Use client...
</script>
Framework Integrations
Vercel AI SDK
Integration with Vercel's AI SDK for streaming chat applications.
// app/api/chat/route.ts
import { streamText } from 'ai';
import { openai } from '@ai-sdk/openai';
import { FulcrumClient } from '@fulcrum-governance/sdk';
const fulcrum = new FulcrumClient({
host: process.env.FULCRUM_HOST!,
apiKey: process.env.FULCRUM_API_KEY,
onFailure: 'FAIL_CLOSED'
});
export async function POST(req: Request) {
const { messages } = await req.json();
const lastMessage = messages[messages.length - 1];
// Create governance envelope
const envelope = await fulcrum.envelope({
workflowId: 'vercel-ai-chat',
metadata: {
source: 'api',
message_count: String(messages.length),
user_id: req.headers.get('x-user-id') || 'anonymous'
}
});
// Pre-check user message
if (!await envelope.guard('process_message', lastMessage.content)) {
envelope.log('message_blocked', {
reason: 'policy_violation',
content_preview: lastMessage.content.substring(0, 50)
});
return new Response('Message blocked by policy', { status: 403 });
}
// Check model usage is allowed
if (!await envelope.guard('use_model', '', { model: 'gpt-4' })) {
return new Response('Model not allowed', { status: 403 });
}
try {
const result = await streamText({
model: openai('gpt-4'),
messages,
onFinish: async ({ usage, text }) => {
// Log completion metrics
envelope.log('llm_completion', {
inputTokens: usage.promptTokens,
outputTokens: usage.completionTokens,
model: 'gpt-4',
responseLength: text.length
});
// Post-check AI response
const responseAllowed = await envelope.guard(
'send_response',
text,
{ response_type: 'ai_generated' }
);
if (!responseAllowed) {
envelope.log('response_filtered', {
reason: 'content_policy'
});
}
await envelope.complete({
status: 'success',
tokens_used: usage.promptTokens + usage.completionTokens
});
}
});
return result.toAIStreamResponse();
} catch (error) {
envelope.log('error', { message: error.message });
await envelope.complete({ status: 'error', error: error.message });
throw error;
}
}
LangChain.js
Integration with LangChain.js for agent orchestration.
import { ChatOpenAI } from '@langchain/openai';
import { AgentExecutor, createOpenAIFunctionsAgent } from 'langchain/agents';
import { DynamicTool } from '@langchain/core/tools';
import { FulcrumClient } from '@fulcrum-governance/sdk';
const fulcrum = new FulcrumClient({
host: process.env.FULCRUM_HOST!,
apiKey: process.env.FULCRUM_API_KEY
});
// Create governed tools
function createGovernedTool(
name: string,
description: string,
func: (input: string) => Promise<string>,
envelope: Envelope
): DynamicTool {
return new DynamicTool({
name,
description,
func: async (input: string) => {
// Check if tool usage is allowed
const allowed = await envelope.guard(
`tool:${name}`,
input,
{ tool_name: name }
);
if (!allowed) {
envelope.log('tool_blocked', { tool: name, input_preview: input.substring(0, 100) });
throw new Error(`Tool ${name} blocked by policy`);
}
envelope.log('tool_start', { tool: name });
const start = Date.now();
try {
const result = await func(input);
envelope.log('tool_complete', {
tool: name,
duration_ms: Date.now() - start,
result_preview: result.substring(0, 100)
});
return result;
} catch (error) {
envelope.log('tool_error', { tool: name, error: error.message });
throw error;
}
}
});
}
// Run governed agent
async function runGovernedAgent(
agent: AgentExecutor,
query: string,
userId: string
): Promise<string> {
const envelope = await fulcrum.envelope({
workflowId: 'langchain-agent',
metadata: { user_id: userId }
});
// Pre-check query
if (!await envelope.guard('process_query', query)) {
envelope.log('query_blocked', { query_preview: query.substring(0, 100) });
throw new Error('Query blocked by policy');
}
const startTime = Date.now();
try {
const result = await agent.invoke({
input: query,
callbacks: [{
handleLLMStart: async (llm, prompts) => {
envelope.log('llm_start', {
model: llm.name,
prompt_count: prompts.length
});
},
handleLLMEnd: async (output) => {
envelope.log('llm_end', {
token_usage: output.llmOutput?.tokenUsage
});
},
handleToolStart: async (tool, input) => {
envelope.log('tool_invoked', { tool: tool.name });
},
handleAgentAction: async (action) => {
envelope.log('agent_action', {
tool: action.tool,
input_preview: JSON.stringify(action.toolInput).substring(0, 100)
});
}
}]
});
// Post-check response
if (!await envelope.guard('send_response', result.output)) {
envelope.log('response_blocked');
throw new Error('Agent response blocked by policy');
}
await envelope.complete({
status: 'success',
duration_ms: Date.now() - startTime,
output_preview: result.output.substring(0, 200)
});
return result.output;
} catch (error) {
await envelope.complete({
status: 'error',
error: error.message,
duration_ms: Date.now() - startTime
});
throw error;
}
}
Express Middleware
Middleware for Express.js applications.
import express, { Request, Response, NextFunction } from 'express';
import { FulcrumClient, Envelope } from '@fulcrum-governance/sdk';
// Extend Express Request type
declare global {
namespace Express {
interface Request {
fulcrumEnvelope?: Envelope;
}
}
}
const app = express();
app.use(express.json());
const fulcrum = new FulcrumClient({
host: process.env.FULCRUM_HOST!,
apiKey: process.env.FULCRUM_API_KEY
});
// Governance middleware factory
function governanceMiddleware(workflowId: string, options?: {
extractInput?: (req: Request) => string;
extractMetadata?: (req: Request) => Record<string, string>;
}) {
return async (req: Request, res: Response, next: NextFunction) => {
const envelope = await fulcrum.envelope({
workflowId,
metadata: {
path: req.path,
method: req.method,
user_id: req.headers['x-user-id'] as string || 'anonymous',
client_ip: req.ip,
...options?.extractMetadata?.(req)
}
});
// Attach to request for use in handlers
req.fulcrumEnvelope = envelope;
// Extract input for policy evaluation
const inputText = options?.extractInput?.(req) || JSON.stringify(req.body);
// Check request against policies
if (!await envelope.guard('api_request', inputText)) {
envelope.log('request_blocked', {
reason: 'policy_violation',
path: req.path
});
return res.status(403).json({
error: 'Request blocked by governance policy',
code: 'POLICY_VIOLATION'
});
}
// Log request start
envelope.log('request_start', {
path: req.path,
method: req.method,
content_length: req.headers['content-length']
});
// Track response
const originalJson = res.json.bind(res);
res.json = function(body: any) {
envelope.log('response_sent', {
status: res.statusCode,
content_length: JSON.stringify(body).length
});
return originalJson(body);
};
// Complete envelope on response finish
res.on('finish', async () => {
await envelope.complete({
status: res.statusCode >= 400 ? 'error' : 'success',
http_status: res.statusCode
});
});
next();
};
}
// Apply to specific routes
app.use('/api/agents', governanceMiddleware('agent-api', {
extractInput: (req) => req.body.prompt || req.body.message || '',
extractMetadata: (req) => ({
agent_type: req.params.agentType || 'default'
})
}));
// Route handler with envelope access
app.post('/api/agents/:agentType/chat', async (req, res) => {
const envelope = req.fulcrumEnvelope!;
const { message } = req.body;
// Use envelope for additional checks
if (!await envelope.guard('generate_response', message)) {
return res.status(403).json({ error: 'Message blocked' });
}
// Process request...
const response = await processAgentMessage(message);
envelope.log('agent_response', {
agent_type: req.params.agentType,
response_length: response.length
});
res.json({ response });
});
app.listen(3000);
Next.js API Routes
Integration with Next.js API routes (App Router).
// app/api/ai/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { FulcrumClient } from '@fulcrum-governance/sdk';
import { headers } from 'next/headers';
const fulcrum = new FulcrumClient({
host: process.env.FULCRUM_HOST!,
apiKey: process.env.FULCRUM_API_KEY
});
// Helper to create envelope from Next.js request
async function createEnvelope(req: NextRequest, workflowId: string) {
const headersList = headers();
return fulcrum.envelope({
workflowId,
metadata: {
user_id: headersList.get('x-user-id') || 'anonymous',
session_id: headersList.get('x-session-id') || '',
user_agent: headersList.get('user-agent') || '',
ip: req.ip || 'unknown',
pathname: req.nextUrl.pathname
}
});
}
export async function POST(req: NextRequest) {
const envelope = await createEnvelope(req, 'nextjs-ai-endpoint');
try {
const body = await req.json();
const { prompt, model = 'gpt-4' } = body;
// Validate input
if (!prompt || typeof prompt !== 'string') {
envelope.log('invalid_request', { reason: 'missing_prompt' });
return NextResponse.json(
{ error: 'Prompt is required' },
{ status: 400 }
);
}
// Check prompt against policies
if (!await envelope.guard('process_prompt', prompt)) {
envelope.log('prompt_blocked');
return NextResponse.json(
{ error: 'Prompt blocked by policy' },
{ status: 403 }
);
}
// Check model usage
if (!await envelope.guard('use_model', '', { model })) {
envelope.log('model_blocked', { model });
return NextResponse.json(
{ error: `Model ${model} not allowed` },
{ status: 403 }
);
}
envelope.log('processing_start', { model, prompt_length: prompt.length });
// Process AI request
const result = await generateAIResponse(prompt, model);
// Post-check response
if (!await envelope.guard('send_response', result.text)) {
envelope.log('response_filtered');
return NextResponse.json(
{ error: 'Response filtered by policy' },
{ status: 403 }
);
}
envelope.log('processing_complete', {
input_tokens: result.inputTokens,
output_tokens: result.outputTokens,
duration_ms: result.durationMs
});
await envelope.complete({ status: 'success' });
return NextResponse.json({
response: result.text,
usage: {
inputTokens: result.inputTokens,
outputTokens: result.outputTokens
}
});
} catch (error) {
envelope.log('error', { message: error.message });
await envelope.complete({ status: 'error', error: error.message });
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}
// Middleware for route protection
// proxy.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export async function middleware(request: NextRequest) {
// For AI routes, add governance headers
if (request.nextUrl.pathname.startsWith('/api/ai')) {
const requestHeaders = new Headers(request.headers);
requestHeaders.set('x-governance-enabled', 'true');
requestHeaders.set('x-request-id', crypto.randomUUID());
return NextResponse.next({
request: {
headers: requestHeaders
}
});
}
}
export const config = {
matcher: '/api/ai/:path*'
};
Fastify
Integration with Fastify framework.
import Fastify, { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
import { FulcrumClient, Envelope } from '@fulcrum-governance/sdk';
const fastify = Fastify({ logger: true });
const fulcrum = new FulcrumClient({
host: process.env.FULCRUM_HOST!,
apiKey: process.env.FULCRUM_API_KEY
});
// Extend Fastify request type
declare module 'fastify' {
interface FastifyRequest {
fulcrumEnvelope?: Envelope;
}
}
// Register governance plugin
fastify.register(async function governancePlugin(fastify: FastifyInstance) {
// Decorate request with envelope
fastify.decorateRequest('fulcrumEnvelope', null);
// Add hook to create envelope
fastify.addHook('preHandler', async (request: FastifyRequest, reply: FastifyReply) => {
// Skip for non-AI routes
if (!request.url.startsWith('/api/ai')) {
return;
}
const envelope = await fulcrum.envelope({
workflowId: 'fastify-ai-service',
metadata: {
request_id: request.id,
path: request.url,
method: request.method,
user_id: request.headers['x-user-id'] as string || 'anonymous'
}
});
request.fulcrumEnvelope = envelope;
// Check request
const body = request.body as Record<string, any> || {};
const inputText = body.prompt || body.message || JSON.stringify(body);
if (!await envelope.guard('api_request', inputText)) {
envelope.log('request_blocked');
reply.code(403).send({
error: 'Request blocked by governance policy',
code: 'POLICY_VIOLATION'
});
return;
}
envelope.log('request_allowed');
});
// Add hook to complete envelope
fastify.addHook('onResponse', async (request: FastifyRequest, reply: FastifyReply) => {
if (request.fulcrumEnvelope) {
await request.fulcrumEnvelope.complete({
status: reply.statusCode >= 400 ? 'error' : 'success',
http_status: reply.statusCode,
duration_ms: reply.getResponseTime()
});
}
});
});
// AI endpoint
interface ChatBody {
message: string;
conversationId?: string;
}
fastify.post<{ Body: ChatBody }>('/api/ai/chat', async (request, reply) => {
const envelope = request.fulcrumEnvelope!;
const { message, conversationId } = request.body;
// Additional guard check
if (!await envelope.guard('generate_response', message)) {
envelope.log('message_blocked');
return reply.code(403).send({ error: 'Message blocked' });
}
envelope.log('processing_message', {
message_length: message.length,
conversation_id: conversationId
});
// Process AI request
const response = await processAIChat(message, conversationId);
envelope.log('response_generated', {
response_length: response.length,
tokens_used: response.tokensUsed
});
return { response: response.text };
});
// Start server
fastify.listen({ port: 3000 }, (err) => {
if (err) {
fastify.log.error(err);
process.exit(1);
}
});
Best Practices
1. Always Use Envelopes for AI Workflows
Every AI agent execution should be wrapped in an envelope for proper governance tracking.
// Good: Using envelope
const envelope = await client.envelope({ workflowId: 'my-agent' });
const allowed = await envelope.guard('action', input);
// Avoid: Direct policy evaluation without envelope
// (loses audit trail and cost tracking)
const result = await client.evaluatePolicy({ ... });
2. Configure Appropriate Failure Modes
Choose the right failure mode based on your application's risk profile.
// High-security applications: Block on any error
const secureClient = new FulcrumClient({
host: 'localhost:50051',
onFailure: 'FAIL_CLOSED'
});
// Lower-risk applications: Allow on error (with logging)
const flexibleClient = new FulcrumClient({
host: 'localhost:50051',
onFailure: 'FAIL_OPEN'
});
3. Use Meaningful Workflow IDs
Workflow IDs should be descriptive and consistent.
// Good: Descriptive workflow IDs
await client.envelope({ workflowId: 'customer-support-chat' });
await client.envelope({ workflowId: 'document-summarization' });
await client.envelope({ workflowId: 'code-review-agent' });
// Avoid: Generic or random IDs
await client.envelope({ workflowId: 'agent1' });
await client.envelope({ workflowId: crypto.randomUUID() });
4. Include Rich Metadata
Provide context for better policy evaluation and auditing.
const envelope = await client.envelope({
workflowId: 'content-moderation',
metadata: {
user_id: userId,
user_role: 'admin',
content_type: 'comment',
source: 'web_app',
department: 'support',
sensitivity_level: 'public'
}
});
5. Log Key Events
Log important events for audit and debugging.
envelope.log('user_input_received', { length: input.length });
envelope.log('model_selected', { model: 'gpt-4', reason: 'complexity' });
envelope.log('tool_invoked', { tool: 'search', query: query.substring(0, 50) });
envelope.log('response_generated', { tokens: outputTokens, latency_ms: latency });
envelope.log('action_completed', { success: true, duration_ms: totalTime });
6. Handle Errors Gracefully
Always handle Fulcrum errors appropriately.
async function governedAction(input: string): Promise<Result> {
let envelope: Envelope | null = null;
try {
envelope = await client.envelope({ workflowId: 'my-workflow' });
if (!await envelope.guard('process', input)) {
return { success: false, reason: 'blocked_by_policy' };
}
const result = await performAction(input);
await envelope.complete({ status: 'success' });
return { success: true, result };
} catch (error) {
if (envelope) {
envelope.log('error', { message: error.message });
await envelope.complete({ status: 'error' });
}
// Handle specific error types
if (error instanceof PolicyViolationError) {
return { success: false, reason: 'policy_violation' };
}
throw error; // Re-throw unknown errors
}
}
7. Use Checkpoints for Long-Running Workflows
Create checkpoints before expensive operations.
async function longRunningWorkflow(input: string) {
const envelope = await client.envelope({ workflowId: 'long-workflow' });
// Phase 1
await envelope.checkpoint();
const step1Result = await expensiveOperation1(input);
// Phase 2
await envelope.checkpoint();
const step2Result = await expensiveOperation2(step1Result);
// Phase 3
await envelope.checkpoint();
const finalResult = await expensiveOperation3(step2Result);
await envelope.complete({ result: finalResult });
return finalResult;
}
8. Monitor Cost Regularly
Check cost during long-running executions.
async function costAwareWorkflow(items: string[]) {
const envelope = await client.envelope({ workflowId: 'batch-processing' });
const MAX_COST_USD = 10.0;
for (const item of items) {
// Check cost before each item
const cost = await envelope.getCost();
if (cost.totalUsd > MAX_COST_USD) {
envelope.log('cost_limit_reached', { current: cost.totalUsd, limit: MAX_COST_USD });
break;
}
await processItem(item);
}
await envelope.complete();
}
9. Use TypeScript for Type Safety
Leverage TypeScript types for compile-time safety.
import {
FulcrumClient,
FulcrumClientOptions,
EnvelopeOptions,
GuardResult,
CostSnapshot
} from '@fulcrum-governance/sdk';
// Type-safe configuration
const options: FulcrumClientOptions = {
host: 'localhost:50051',
apiKey: process.env.FULCRUM_API_KEY,
onFailure: 'FAIL_CLOSED',
timeoutMs: 1000
};
// Type-safe envelope options
const envelopeOptions: EnvelopeOptions = {
workflowId: 'typed-workflow',
metadata: {
key: 'value' // Record<string, string>
}
};
10. Singleton Client Pattern
Use a single client instance across your application.
// lib/fulcrum.ts
import { FulcrumClient } from '@fulcrum-governance/sdk';
let client: FulcrumClient | null = null;
export function getFulcrumClient(): FulcrumClient {
if (!client) {
client = new FulcrumClient({
host: process.env.FULCRUM_HOST!,
apiKey: process.env.FULCRUM_API_KEY,
onFailure: process.env.NODE_ENV === 'production' ? 'FAIL_CLOSED' : 'FAIL_OPEN',
timeoutMs: parseInt(process.env.FULCRUM_TIMEOUT_MS || '500')
});
}
return client;
}
// Cleanup on shutdown
export function shutdownFulcrum(): void {
if (client) {
client.shutdown();
client = null;
}
}
// Usage in other files
import { getFulcrumClient } from './lib/fulcrum';
const client = getFulcrumClient();
Troubleshooting
Connection Issues
Problem: ConnectionError: Failed to connect to Fulcrum server
Solutions:
-
Verify the server is running:
-
Check host format (should be
host:port): -
Check TLS configuration:
-
Check firewall rules allow the port.
Authentication Errors
Problem: AuthenticationError: Invalid API key
Solutions:
-
Verify API key is set:
-
Check for whitespace in API key:
-
Verify API key is valid in Fulcrum dashboard.
Timeout Issues
Problem: TimeoutError: Request timed out
Solutions:
-
Increase timeout:
-
Check server health:
-
Check network latency to server.
Proto Loading Errors
Problem: Error: Proto file not found
Solutions:
-
Ensure proto files are included in the package:
-
Reinstall the package:
Policy Evaluation Issues
Problem: guard() always returns false
Solutions:
-
Check tenant ID matches policies:
-
Check workflow ID is in policy scope:
-
Enable debug logging:
Memory Leaks
Problem: Memory usage grows over time
Solutions:
-
Always complete envelopes:
-
Shutdown client when done:
-
Use try-finally pattern:
Common Error Codes
| Error Code | Meaning | Solution |
|---|---|---|
UNAVAILABLE |
Server unreachable | Check connectivity |
UNAUTHENTICATED |
Invalid credentials | Verify API key |
PERMISSION_DENIED |
Access denied | Check tenant/permissions |
DEADLINE_EXCEEDED |
Timeout | Increase timeoutMs |
INVALID_ARGUMENT |
Bad request | Check request format |
NOT_FOUND |
Resource missing | Verify IDs exist |
RESOURCE_EXHAUSTED |
Rate limited | Implement backoff |
Debug Mode
Enable debug logging for troubleshooting:
// Set environment variable before importing
process.env.FULCRUM_DEBUG = 'true';
import { FulcrumClient } from '@fulcrum-governance/sdk';
// Or enable programmatically
const client = new FulcrumClient({
host: 'localhost:50051',
debug: true // Enable verbose logging
});
Getting Support
If you continue to experience issues:
- Check the Fulcrum documentation
- Review GitHub issues
- Contact support: support@fulcrum.dev
Version History
| Version | Date | Changes |
|---|---|---|
| 0.1.3 | 2026-02 | Current release |
| 0.1.1 | 2026-01 | Initial public release |
Documentation generated for Fulcrum TypeScript SDK v0.1.3 Last updated: February 2026