SidClaw

withGovernance()

Wrap any async function with automatic policy evaluation, approval waiting, and outcome recording.

withGovernance()

withGovernance wraps an async function with full governance lifecycle management. It evaluates the action, waits for approval if needed, executes the function, and records the outcome -- all automatically.

This is the recommended way to integrate SidClaw into your agent. For lower-level control, use evaluate() directly.

Import

import { AgentIdentityClient, withGovernance } from '@sidclaw/sdk';

Signature

function withGovernance<TArgs extends unknown[], TResult>(
  client: AgentIdentityClient,
  config: GovernanceConfig,
  fn: (...args: TArgs) => Promise<TResult>,
): (...args: TArgs) => Promise<TResult>;

Returns a new function with the same signature as fn, but governed by the SidClaw policy engine.

GovernanceConfig

PropertyTypeRequiredDescription
operationstringYesThe action being performed (e.g., 'send_email')
target_integrationstringYesThe system being acted on (e.g., 'sendgrid')
resource_scopestringYesScope of the resource (e.g., 'customer_emails')
data_classificationDataClassificationYesOne of: 'public', 'internal', 'confidential', 'restricted'
contextRecord<string, unknown>NoAdditional context for policy evaluation and audit
approvalOptions{ timeout?: number; pollInterval?: number }NoOptions for approval polling (see waitForApproval)

Example

import { AgentIdentityClient, withGovernance } from '@sidclaw/sdk';

const client = new AgentIdentityClient({
  apiKey: process.env.AGENT_IDENTITY_API_KEY!,
  apiUrl: 'https://api.sidclaw.com',
  agentId: 'ag_customer-support-bot',
});

// Wrap your function with governance
const sendEmail = withGovernance(
  client,
  {
    operation: 'send_email',
    target_integration: 'sendgrid',
    resource_scope: 'customer_emails',
    data_classification: 'confidential',
    context: {
      reason: 'order_confirmation',
    },
    approvalOptions: {
      timeout: 600_000, // 10 minutes
    },
  },
  async (to: string, subject: string, body: string) => {
    // Your actual email-sending logic
    const response = await fetch('https://api.sendgrid.com/v3/mail/send', {
      method: 'POST',
      headers: { Authorization: `Bearer ${process.env.SENDGRID_API_KEY}` },
      body: JSON.stringify({ to, subject, body }),
    });
    return response.json();
  },
);

// Call it like normal -- governance happens transparently
await sendEmail('[email protected]', 'Order shipped', 'Your order is on the way!');

How It Works

When the governed function is called:

  1. Evaluate: Calls client.evaluate() with the action details from GovernanceConfig.
  2. Decision handling:
    • allow: Executes the wrapped function immediately.
    • approval_required: Calls client.waitForApproval() and blocks until a human approves or denies. If approved, executes the function. If denied or expired, throws.
    • deny: Throws ActionDeniedError without executing the function.
  3. Outcome recording: After execution, calls client.recordOutcome() with 'success' or 'error'.

Error Handling

import {
  AgentIdentityClient,
  withGovernance,
  ActionDeniedError,
  ApprovalTimeoutError,
  ApprovalExpiredError,
} from '@sidclaw/sdk';

const governedAction = withGovernance(client, config, myFunction);

try {
  const result = await governedAction(arg1, arg2);
} catch (error) {
  if (error instanceof ActionDeniedError) {
    // Policy denied the action, or a reviewer denied the approval
    console.log('Denied:', error.reason);
    console.log('Trace:', error.traceId);
  } else if (error instanceof ApprovalTimeoutError) {
    // Timed out waiting for a reviewer
    console.log('Timed out after', error.timeoutMs, 'ms');
  } else if (error instanceof ApprovalExpiredError) {
    // The approval request expired server-side
    console.log('Expired:', error.approvalRequestId);
  } else {
    // The wrapped function itself threw an error
    // (outcome is already recorded as 'error')
    throw error;
  }
}

Notes

  • The wrapped function's return type and arguments are preserved. TypeScript infers them automatically.
  • If the wrapped function throws, withGovernance records the outcome as 'error' (with the error message in metadata) and then re-throws the original error.
  • The context in GovernanceConfig is static -- it is the same for every invocation. If you need per-call context, use evaluate() directly.