@haltstate/sdk
v1.0.2
Published
HaltState SDK - Runtime governance for AI agents
Maintainers
Readme
@haltstate/sdk
Official TypeScript/JavaScript SDK for HaltState - Human-in-the-loop governance for AI agents.
Installation
npm install @haltstate/sdk
# or
yarn add @haltstate/sdk
# or
pnpm add @haltstate/sdkQuick Start
import { HaltStateClient, ApprovalPending, ActionDenied } from '@haltstate/sdk';
const client = new HaltStateClient({
tenantId: 'your-tenant-id',
apiKey: 'hs_your_api_key',
agentId: 'payment-processor-01',
});
// Guard a high-risk action
try {
const permit = await client.guard('process_payment', {
params: { amount: 5000, currency: 'USD', recipient: '[email protected]' },
idempotencyKey: `payment-${orderId}`,
});
// Action approved - execute it
await processPayment(orderId);
// Report success with the permit
await client.report(permit, { success: true, transactionId: txId });
} catch (error) {
if (error instanceof ApprovalPending) {
console.log(`Waiting for approval: ${error.approvalId}`);
// Exit gracefully - retry later via scheduler
process.exit(0);
}
if (error instanceof ActionDenied) {
console.log(`Action denied: ${error.reason}`);
// Handle denial
}
throw error;
}Configuration
Constructor Options
const client = new HaltStateClient({
// Required
tenantId: 'your-tenant-id', // Your HaltState tenant identifier
apiKey: 'hs_your_api_key', // API key (prefix: hs_)
// Optional
baseUrl: 'https://haltstate.ai/ops', // API endpoint (default)
timeout: 30, // Request timeout in seconds (default: 30)
failOpen: false, // Allow actions when API unreachable (default: false)
retryCount: 3, // Retry attempts for transient errors (default: 3)
agentId: 'my-agent', // Identifier for this agent instance
enableKillSwitch: false, // Enable background heartbeat monitoring
killSwitchInterval: 30, // Heartbeat interval in seconds (default: 30)
});Environment Variables
The SDK reads these environment variables as fallbacks:
HALTSTATE_TENANT_ID=your-tenant-id
HALTSTATE_API_KEY=hs_your_api_key
HALTSTATE_BASE_URL=https://haltstate.ai/ops
HALTSTATE_AGENT_ID=my-agentCore Methods
check(action, params?)
Check if an action is allowed without blocking. Returns a CheckResult.
const result = await client.check('delete_database', {
database: 'production',
reason: 'cleanup'
});
if (result.decision === Decision.ALLOW) {
// Safe to proceed
} else if (result.decision === Decision.APPROVAL_REQUIRED) {
// Need human approval - result.approvalId contains the request ID
} else {
// Denied by policy
}guard(action, options?)
Guard an action with automatic approval handling. Throws control flow exceptions.
import { ApprovalPending, ActionDenied, ActionExpired } from '@haltstate/sdk';
try {
const permit = await client.guard('deploy_to_production', {
params: { version: '2.0.0', environment: 'prod' },
idempotencyKey: `deploy-${buildId}`,
agentId: 'deploy-agent',
});
// Permit received - action is approved
console.log(`Approved by ${permit.approver} at ${permit.approvedAt}`);
} catch (error) {
if (error instanceof ApprovalPending) {
// Action requires human approval - exit and retry later
console.log(`Approval pending: ${error.approvalId}`);
return;
}
if (error instanceof ActionDenied) {
// Blocked by policy
console.log(`Denied: ${error.reason} (policy: ${error.policyId})`);
return;
}
if (error instanceof ActionExpired) {
// Previous approval expired - need to re-request
console.log(`Expired: ${error.idempotencyKey}`);
return;
}
throw error;
}withGuard(action, fn, options?)
Execute a function only if the action is approved. Automatically reports the outcome.
const result = await client.withGuard(
'send_email_blast',
async (permit) => {
// This only runs if approved
const sent = await sendEmails(campaignId);
return { emailsSent: sent };
},
{
params: { campaign: campaignId, recipients: 50000 },
idempotencyKey: `campaign-${campaignId}`,
}
);waitForApproval(approvalId, options?)
Block until an approval is decided (approved/rejected/expired).
try {
const decision = await client.waitForApproval(approvalId, {
timeout: 300, // 5 minutes
pollInterval: 2, // Start with 2s polling
maxPollInterval: 10, // Max 10s between polls
});
if (decision.status === ApprovalStatus.APPROVED) {
console.log(`Approved by ${decision.approver}`);
}
} catch (error) {
if (error instanceof HaltStateApprovalTimeoutError) {
console.log('Approval not received within timeout');
}
}report(permit, options?)
Report the outcome of an approved action for audit trail.
await client.report(permit, {
result: {
success: true,
recordsProcessed: 1500,
duration_ms: 3200,
},
});onApproval(callback, options?)
Subscribe to real-time approval decisions via SSE.
const unsubscribe = client.onApproval(
(decision) => {
console.log(`Decision for ${decision.requestId}: ${decision.status}`);
if (decision.status === ApprovalStatus.APPROVED) {
// Resume the pending action
resumeAction(decision.requestId);
}
},
{ catchUpHours: 1 } // Also receive decisions from last hour
);
// Later: stop listening
unsubscribe();getApprovalsSince(hours)
Get historical approval decisions.
const decisions = await client.getApprovalsSince(24); // Last 24 hours
for (const decision of decisions) {
console.log(`${decision.action}: ${decision.status}`);
}close()
Clean up resources (stop heartbeat, close SSE connections).
client.close();Error Handling
Exception Hierarchy
HaltStateError (base)
├── HaltStateConnectionError - Network/connection failures
├── HaltStateAuthError - Invalid API key (401)
├── HaltStateRateLimitError - Rate limit exceeded (429)
├── HaltStateApprovalTimeoutError - waitForApproval() timeout
├── ApprovalPending - Action awaiting human approval (not an error)
├── ActionDenied - Action blocked by policy
├── ActionExpired - Previous approval expired
└── KillSwitchTriggered - Remote kill switch activatedType Guards
import { isControlFlowSignal, isHaltStateError } from '@haltstate/sdk';
try {
await client.guard('action');
} catch (error) {
if (isControlFlowSignal(error)) {
// ApprovalPending, ActionDenied, or ActionExpired
// These are expected control flow, not errors
return;
}
if (isHaltStateError(error)) {
// HaltState-specific error
console.error('HaltState error:', error.message);
}
throw error;
}Kill Switch
Enable the kill switch to allow remote termination of your agent:
const client = new HaltStateClient({
tenantId: 'your-tenant',
apiKey: 'hs_key',
agentId: 'critical-agent-01',
enableKillSwitch: true,
killSwitchInterval: 30, // Check every 30 seconds
});
// The client will automatically throw KillSwitchTriggered
// if a kill signal is received from the HaltState dashboardExamples
Cron Job Pattern
// payment-processor.ts
import { HaltStateClient, ApprovalPending, ActionDenied } from '@haltstate/sdk';
async function processScheduledPayments() {
const client = new HaltStateClient({
tenantId: process.env.HALTSTATE_TENANT_ID!,
apiKey: process.env.HALTSTATE_API_KEY!,
agentId: 'payment-cron',
});
const payments = await getScheduledPayments();
for (const payment of payments) {
try {
const permit = await client.guard('process_payment', {
params: { amount: payment.amount, recipient: payment.to },
idempotencyKey: `payment-${payment.id}`,
});
await executePayment(payment);
await client.report(permit, { success: true });
} catch (error) {
if (error instanceof ApprovalPending) {
// Will retry on next cron run
console.log(`Payment ${payment.id} awaiting approval`);
continue;
}
if (error instanceof ActionDenied) {
await markPaymentDenied(payment.id, error.reason);
continue;
}
throw error;
}
}
client.close();
}Real-time Approval Handler
// approval-handler.ts
import { HaltStateClient, ApprovalStatus } from '@haltstate/sdk';
const client = new HaltStateClient({
tenantId: process.env.HALTSTATE_TENANT_ID!,
apiKey: process.env.HALTSTATE_API_KEY!,
agentId: 'approval-handler',
});
// Listen for approval decisions and resume pending work
client.onApproval(async (decision) => {
if (decision.status === ApprovalStatus.APPROVED) {
const pendingWork = await getPendingWork(decision.requestId);
if (pendingWork) {
await executePendingWork(pendingWork);
}
}
}, { catchUpHours: 1 });
// Keep process alive
process.on('SIGTERM', () => client.close());TypeScript Support
This package includes full TypeScript definitions. All types are exported:
import type {
HaltStateConfig,
CheckResult,
Permit,
ApprovalDecision,
GuardOptions,
WaitOptions,
} from '@haltstate/sdk';License
MIT
