Skip to content

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

  1. Installation
  2. Configuration
  3. TypeScript Types
  4. Core Classes
  5. FulcrumClient
  6. Envelope
  7. Methods Reference
  8. Error Handling
  9. Browser Support
  10. Framework Integrations
  11. Vercel AI SDK
  12. LangChain.js
  13. Express Middleware
  14. Next.js API Routes
  15. Fastify
  16. Best Practices
  17. Troubleshooting

Installation

npm

npm install @fulcrum-governance/sdk

yarn

yarn add @fulcrum-governance/sdk

pnpm

pnpm add @fulcrum-governance/sdk

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:

const client = new FulcrumClient({
  host: 'localhost:50051'
});

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

class FulcrumClient {
  constructor(options: FulcrumClientOptions);
}

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.

async envelope(options: EnvelopeOptions): Promise<Envelope>

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.

log(eventType: string, payload?: Record<string, any>): void

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.

async getCost(): Promise<CostSnapshot>

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.

async checkpoint(): Promise<Checkpoint>

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.

async complete(result?: any): Promise<void>

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:

import { FulcrumRestClient } from '@fulcrum-governance/sdk/rest';

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:

  1. Verify the server is running:

    grpcurl -plaintext localhost:50051 list
    

  2. Check host format (should be host:port):

    // Correct
    const client = new FulcrumClient({ host: 'localhost:50051' });
    
    // Incorrect (missing port)
    const client = new FulcrumClient({ host: 'localhost' });
    

  3. Check TLS configuration:

    // For production with TLS
    const client = new FulcrumClient({
      host: 'fulcrum.example.com:443',
      enableTls: true
    });
    
    // For local development without TLS
    const client = new FulcrumClient({
      host: 'localhost:50051',
      enableTls: false
    });
    

  4. Check firewall rules allow the port.

Authentication Errors

Problem: AuthenticationError: Invalid API key

Solutions:

  1. Verify API key is set:

    console.log('API Key:', process.env.FULCRUM_API_KEY ? 'Set' : 'Not set');
    

  2. Check for whitespace in API key:

    const apiKey = process.env.FULCRUM_API_KEY?.trim();
    

  3. Verify API key is valid in Fulcrum dashboard.

Timeout Issues

Problem: TimeoutError: Request timed out

Solutions:

  1. Increase timeout:

    const client = new FulcrumClient({
      host: 'localhost:50051',
      timeoutMs: 2000  // Increase from default 500ms
    });
    

  2. Check server health:

    try {
      await client.healthCheck();
    } catch (e) {
      console.error('Server unhealthy:', e);
    }
    

  3. Check network latency to server.

Proto Loading Errors

Problem: Error: Proto file not found

Solutions:

  1. Ensure proto files are included in the package:

    ls node_modules/@fulcrum-governance/sdk/proto/
    

  2. Reinstall the package:

    rm -rf node_modules/@fulcrum-governance/sdk
    npm install @fulcrum-governance/sdk
    

Policy Evaluation Issues

Problem: guard() always returns false

Solutions:

  1. Check tenant ID matches policies:

    const envelope = await client.envelope({
      workflowId: 'my-workflow',
      tenantId: 'correct-tenant-id'  // Must match policy tenant
    });
    

  2. Check workflow ID is in policy scope:

    // Ensure 'my-workflow' is included in policy scope
    

  3. Enable debug logging:

    const result = await client.evaluatePolicy({
      tenantId: 'tenant-123',
      workflowId: 'my-workflow',
      inputText: 'test input'
    });
    console.log('Decision:', result.decision);
    console.log('Reason:', result.reason);
    

Memory Leaks

Problem: Memory usage grows over time

Solutions:

  1. Always complete envelopes:

    const envelope = await client.envelope({ workflowId: 'my-workflow' });
    try {
      // ... operations ...
    } finally {
      await envelope.complete();  // Always complete
    }
    

  2. Shutdown client when done:

    // On application shutdown
    client.shutdown();
    

  3. Use try-finally pattern:

    async function processRequest(input: string) {
      const envelope = await client.envelope({ workflowId: 'request-handler' });
      try {
        // ... process ...
        return result;
      } finally {
        await envelope.complete();
      }
    }
    

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:

  1. Check the Fulcrum documentation
  2. Review GitHub issues
  3. 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