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
| Property | Type | Required | Description |
|---|---|---|---|
operation | string | Yes | The action being performed (e.g., 'send_email') |
target_integration | string | Yes | The system being acted on (e.g., 'sendgrid') |
resource_scope | string | Yes | Scope of the resource (e.g., 'customer_emails') |
data_classification | DataClassification | Yes | One of: 'public', 'internal', 'confidential', 'restricted' |
context | Record<string, unknown> | No | Additional context for policy evaluation and audit |
approvalOptions | { timeout?: number; pollInterval?: number } | No | Options 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:
- Evaluate: Calls
client.evaluate()with the action details fromGovernanceConfig. - Decision handling:
allow: Executes the wrapped function immediately.approval_required: Callsclient.waitForApproval()and blocks until a human approves or denies. If approved, executes the function. If denied or expired, throws.deny: ThrowsActionDeniedErrorwithout executing the function.
- 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,
withGovernancerecords the outcome as'error'(with the error message in metadata) and then re-throws the original error. - The
contextinGovernanceConfigis static -- it is the same for every invocation. If you need per-call context, useevaluate()directly.