Skip to main content
Coming Soon

The Node.js SDK (@ascend/sdk) is not yet published to npm. This documentation is a preview of the planned API. For production use, please use the Python SDK or the REST API.

Node.js SDK Examples

Production-ready TypeScript code examples for common use cases with the ASCEND Node.js SDK.

Table of Contents


Basic Action Evaluation

Simple pattern for evaluating and executing governed actions.

/**
* Basic action evaluation pattern
*/
import { AscendClient, Decision } from '@ascend/sdk';

const client = new AscendClient({
apiKey: process.env.ASCEND_API_KEY!,
agentId: 'basic-agent',
agentName: 'Basic Agent'
});

interface ActionResult {
status: 'success' | 'pending' | 'denied' | 'error';
data?: unknown;
approvalId?: string;
reason?: string;
}

async function governedAction(
actionType: string,
resource: string,
params: Record<string, unknown>
): Promise<ActionResult> {
const decision = await client.evaluateAction({
actionType,
resource,
parameters: params
});

if (decision.decision === Decision.ALLOWED) {
const startTime = Date.now();

try {
const result = await executeAction(actionType, resource, params);

await client.logActionCompleted(
decision.actionId,
{ success: true },
Date.now() - startTime
);

return { status: 'success', data: result };

} catch (error) {
await client.logActionFailed(
decision.actionId,
error as Error,
Date.now() - startTime
);

return { status: 'error', reason: (error as Error).message };
}
}

if (decision.decision === Decision.PENDING) {
return {
status: 'pending',
approvalId: decision.approvalRequestId,
reason: 'Awaiting human approval'
};
}

return {
status: 'denied',
reason: decision.reason
};
}

async function executeAction(
actionType: string,
resource: string,
params: Record<string, unknown>
): Promise<unknown> {
// Your action implementation
return { executed: true };
}

// Usage
const result = await governedAction(
'database.query',
'production_db',
{ query: 'SELECT * FROM users LIMIT 10' }
);
console.log(result);

Express.js Middleware

Integrate ASCEND governance into Express.js applications.

/**
* Express.js middleware for ASCEND governance
*/
import express, { Request, Response, NextFunction } from 'express';
import { AscendClient, Decision, FailMode } from '@ascend/sdk';

const app = express();
app.use(express.json());

// Initialize ASCEND client
const ascend = new AscendClient({
apiKey: process.env.ASCEND_API_KEY!,
agentId: 'express-api',
agentName: 'Express API Server',
failMode: FailMode.CLOSED
});

// Register on startup
await ascend.register({
agentType: 'api_server',
capabilities: ['api.read', 'api.write', 'api.delete'],
allowedResources: ['users', 'orders', 'products']
});

// Governance middleware factory
function requireGovernance(actionType: string, resource: string) {
return async (req: Request, res: Response, next: NextFunction) => {
try {
const decision = await ascend.evaluateAction({
actionType,
resource,
parameters: {
method: req.method,
path: req.path,
body: req.body
},
context: {
ip: req.ip,
userAgent: req.get('user-agent'),
sessionId: req.get('x-session-id')
}
});

if (decision.decision === Decision.ALLOWED) {
// Attach action ID for logging
res.locals.actionId = decision.actionId;
res.locals.actionStartTime = Date.now();
return next();
}

if (decision.decision === Decision.PENDING) {
return res.status(202).json({
status: 'pending_approval',
approvalId: decision.approvalRequestId,
message: 'Request requires approval'
});
}

return res.status(403).json({
status: 'denied',
reason: decision.reason,
policyViolations: decision.policyViolations
});

} catch (error) {
console.error('Governance error:', error);
return res.status(503).json({
status: 'error',
message: 'Governance service unavailable'
});
}
};
}

// Completion logging middleware
function logCompletion() {
return async (req: Request, res: Response, next: NextFunction) => {
res.on('finish', async () => {
const { actionId, actionStartTime } = res.locals;

if (!actionId) return;

const durationMs = Date.now() - actionStartTime;

if (res.statusCode < 400) {
await ascend.logActionCompleted(actionId, {
statusCode: res.statusCode
}, durationMs);
} else {
await ascend.logActionFailed(actionId, {
statusCode: res.statusCode,
error: 'Request failed'
});
}
});

next();
};
}

// Apply middleware
app.use(logCompletion());

// Routes with governance
app.get('/api/users',
requireGovernance('api.read', 'users'),
async (req, res) => {
const users = await db.query('SELECT * FROM users');
res.json(users);
}
);

app.post('/api/users',
requireGovernance('api.write', 'users'),
async (req, res) => {
const user = await db.insert('users', req.body);
res.status(201).json(user);
}
);

app.delete('/api/users/:id',
requireGovernance('api.delete', 'users'),
async (req, res) => {
await db.delete('users', req.params.id);
res.status(204).send();
}
);

app.listen(3000, () => {
console.log('Server running with ASCEND governance');
});

Customer Service Agent

Complete customer service agent with TypeScript.

/**
* Customer Service Agent with ASCEND Governance
*/
import { AscendClient, FailMode, Decision } from '@ascend/sdk';

interface RefundRequest {
customerId: string;
orderId: string;
amount: number;
currency: string;
reason: string;
}

interface RefundResult {
status: 'completed' | 'pending_approval' | 'denied' | 'error';
refundId?: string;
approvalId?: string;
message?: string;
}

class CustomerServiceAgent {
private client: AscendClient;

constructor(apiKey: string, environment: string = 'production') {
this.client = new AscendClient({
apiKey,
agentId: 'customer-service-agent',
agentName: 'Customer Service Bot',
environment,
failMode: FailMode.CLOSED
});
}

async initialize(): Promise<void> {
await this.client.register({
agentType: 'automation',
capabilities: [
'transaction.refund',
'customer.lookup',
'order.status'
],
allowedResources: [
'stripe_api',
'customer_db',
'order_db'
]
});
}

async processRefund(request: RefundRequest): Promise<RefundResult> {
const decision = await this.client.evaluateAction({
actionType: 'transaction.refund',
resource: 'stripe_api',
parameters: {
customerId: request.customerId,
orderId: request.orderId,
amount: request.amount,
currency: request.currency,
reason: request.reason
},
context: {
userRequest: `Refund $${request.amount} for order ${request.orderId}`
}
});

if (decision.decision === Decision.ALLOWED) {
return this.executeRefund(decision.actionId, request);
}

if (decision.decision === Decision.PENDING) {
return {
status: 'pending_approval',
approvalId: decision.approvalRequestId,
message: `Refund requires approval. ID: ${decision.approvalRequestId}`
};
}

return {
status: 'denied',
message: decision.reason
};
}

private async executeRefund(
actionId: string,
request: RefundRequest
): Promise<RefundResult> {
const startTime = Date.now();

try {
// Simulate refund processing
const refundId = `ref_${Date.now()}`;

await this.client.logActionCompleted(
actionId,
{ refundId, amount: request.amount },
Date.now() - startTime
);

return {
status: 'completed',
refundId,
message: `Refund ${refundId} processed successfully`
};

} catch (error) {
await this.client.logActionFailed(
actionId,
error as Error,
Date.now() - startTime
);

return {
status: 'error',
message: (error as Error).message
};
}
}

async lookupCustomer(customerId: string): Promise<Customer | ErrorResult> {
const decision = await this.client.evaluateAction({
actionType: 'customer.lookup',
resource: 'customer_db',
parameters: { customerId }
});

if (decision.decision === Decision.ALLOWED) {
return {
customerId,
name: 'John Doe',
email: 'john@example.com'
};
}

return { error: decision.reason };
}
}

interface Customer {
customerId: string;
name: string;
email: string;
}

interface ErrorResult {
error: string | undefined;
}

// Usage
const agent = new CustomerServiceAgent(process.env.ASCEND_API_KEY!);
await agent.initialize();

const result = await agent.processRefund({
customerId: 'cust_123',
orderId: 'ord_456',
amount: 75.00,
currency: 'USD',
reason: 'Product defect'
});

console.log(result);

Async Batch Processing

Process multiple actions concurrently.

/**
* Async batch processing with ASCEND
*/
import { AscendClient, Decision } from '@ascend/sdk';

interface BatchItem {
actionType: string;
resource: string;
parameters: Record<string, unknown>;
}

interface BatchResult {
index: number;
status: 'success' | 'denied' | 'error';
actionId?: string;
data?: unknown;
error?: string;
}

class BatchProcessor {
private client: AscendClient;

constructor(apiKey: string) {
this.client = new AscendClient({
apiKey,
agentId: 'batch-processor',
agentName: 'Batch Processor Agent'
});
}

async processBatch(
items: BatchItem[],
options: { concurrency?: number; continueOnError?: boolean } = {}
): Promise<BatchResult[]> {
const { concurrency = 5, continueOnError = true } = options;
const results: BatchResult[] = [];

// Process in chunks for controlled concurrency
for (let i = 0; i < items.length; i += concurrency) {
const chunk = items.slice(i, i + concurrency);

const chunkResults = await Promise.all(
chunk.map((item, idx) =>
this.processItem(item, i + idx, continueOnError)
)
);

results.push(...chunkResults);

// Check if we should stop on error
if (!continueOnError && chunkResults.some(r => r.status === 'error')) {
break;
}
}

return results;
}

private async processItem(
item: BatchItem,
index: number,
continueOnError: boolean
): Promise<BatchResult> {
try {
const decision = await this.client.evaluateAction({
actionType: item.actionType,
resource: item.resource,
parameters: item.parameters
});

if (decision.decision === Decision.ALLOWED) {
const data = await this.executeAction(item);

await this.client.logActionCompleted(decision.actionId, {
success: true
});

return {
index,
status: 'success',
actionId: decision.actionId,
data
};
}

return {
index,
status: 'denied',
actionId: decision.actionId,
error: decision.reason
};

} catch (error) {
return {
index,
status: 'error',
error: (error as Error).message
};
}
}

private async executeAction(item: BatchItem): Promise<unknown> {
// Implement actual action
return { processed: true };
}
}

// Usage
const processor = new BatchProcessor(process.env.ASCEND_API_KEY!);

const items: BatchItem[] = [
{ actionType: 'database.query', resource: 'db1', parameters: { query: 'Q1' } },
{ actionType: 'file.read', resource: '/logs', parameters: { path: '/app.log' } },
{ actionType: 'api.call', resource: 'api', parameters: { endpoint: '/status' } }
];

const results = await processor.processBatch(items, { concurrency: 3 });
console.log(`Processed: ${results.filter(r => r.status === 'success').length}/${results.length}`);

Webhook Handler

Receive and process ASCEND webhooks.

/**
* ASCEND webhook handler with Express
*/
import express, { Request, Response } from 'express';
import crypto from 'crypto';

const app = express();

const WEBHOOK_SECRET = process.env.ASCEND_WEBHOOK_SECRET!;

// Raw body parser for signature verification
app.use('/webhooks/ascend', express.raw({ type: 'application/json' }));

// Verify webhook signature
function verifySignature(payload: Buffer, timestamp: string, signature: string): boolean {
const expected = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(`${timestamp}.${payload.toString()}`)
.digest('hex');

return crypto.timingSafeEqual(
Buffer.from(`v1=${expected}`),
Buffer.from(signature)
);
}

// Event handlers
type EventHandler = (data: Record<string, unknown>) => Promise<void>;

const eventHandlers: Record<string, EventHandler> = {
'action.approved': async (data) => {
console.log(`Action ${data.action_id} approved by ${data.approver}`);
await executePendingAction(data.action_id as string);
},

'action.denied': async (data) => {
console.log(`Action ${data.action_id} denied: ${data.reason}`);
await notifyUserOfDenial(data.action_id as string, data.reason as string);
},

'policy.violation': async (data) => {
console.log(`Policy violation by ${data.agent_id}: ${data.violations}`);
await alertSecurityTeam(data.agent_id as string, data.violations as string[]);
},

'agent.trust_changed': async (data) => {
console.log(`Agent ${data.agent_id} trust: ${data.old_level} -> ${data.new_level}`);
}
};

// Webhook endpoint
app.post('/webhooks/ascend', async (req: Request, res: Response) => {
const signature = req.headers['x-signature'] as string;
const timestamp = req.headers['x-timestamp'] as string;

// Verify signature
if (!verifySignature(req.body, timestamp, signature)) {
return res.status(401).json({ error: 'Invalid signature' });
}

const payload = JSON.parse(req.body.toString());
const eventType = payload.event;
const data = payload.data;

// Route to handler
const handler = eventHandlers[eventType];
if (handler) {
try {
await handler(data);
return res.json({ status: 'processed' });
} catch (error) {
console.error(`Handler error: ${error}`);
return res.status(500).json({ error: 'Processing failed' });
}
}

return res.json({ status: 'ignored', event: eventType });
});

// Placeholder functions
async function executePendingAction(actionId: string): Promise<void> {}
async function notifyUserOfDenial(actionId: string, reason: string): Promise<void> {}
async function alertSecurityTeam(agentId: string, violations: string[]): Promise<void> {}

app.listen(8080, () => {
console.log('Webhook server running on port 8080');
});

Error Handling Patterns

Comprehensive error handling with TypeScript.

/**
* Resilient error handling patterns
*/
import {
AscendClient,
Decision,
FailMode,
AuthenticationError,
AuthorizationError,
TimeoutError,
RateLimitError,
ConnectionError,
CircuitBreakerOpenError,
ValidationError
} from '@ascend/sdk';

interface ActionResult {
status: 'success' | 'denied' | 'error' | 'blocked';
data?: unknown;
errorType?: string;
message?: string;
recoverable?: boolean;
}

class ResilientAgent {
private client: AscendClient;
private failOpen: boolean;

constructor(apiKey: string, failOpen: boolean = false) {
this.client = new AscendClient({
apiKey,
agentId: 'resilient-agent',
agentName: 'Resilient Agent',
failMode: failOpen ? FailMode.OPEN : FailMode.CLOSED,
timeout: 5000,
maxRetries: 3
});
this.failOpen = failOpen;
}

async governedAction(
actionType: string,
resource: string,
parameters: Record<string, unknown>,
maxRetries: number = 3
): Promise<ActionResult> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await this.executeWithGovernance(actionType, resource, parameters);

} catch (error) {
const result = this.handleError(error as Error, attempt, maxRetries);
if (result) return result;

// Wait before retry
await this.sleep(Math.pow(2, attempt) * 1000);
}
}

return this.handleServiceUnavailable('max_retries');
}

private async executeWithGovernance(
actionType: string,
resource: string,
parameters: Record<string, unknown>
): Promise<ActionResult> {
const decision = await this.client.evaluateAction({
actionType,
resource,
parameters
});

if (decision.decision === Decision.ALLOWED) {
return { status: 'success', data: { executed: true } };
}

if (decision.decision === Decision.PENDING) {
return {
status: 'denied',
message: 'Awaiting approval',
recoverable: true
};
}

return {
status: 'denied',
message: decision.reason,
recoverable: false
};
}

private handleError(
error: Error,
attempt: number,
maxRetries: number
): ActionResult | null {
if (error instanceof AuthenticationError) {
return {
status: 'error',
errorType: 'authentication',
message: 'Invalid API credentials',
recoverable: false
};
}

if (error instanceof AuthorizationError) {
return {
status: 'denied',
errorType: 'authorization',
message: error.message,
recoverable: false
};
}

if (error instanceof ValidationError) {
return {
status: 'error',
errorType: 'validation',
message: error.message,
recoverable: false
};
}

if (error instanceof RateLimitError) {
// Don't return - will retry
console.log(`Rate limited. Waiting ${error.retryAfter}s...`);
return null;
}

if (error instanceof TimeoutError) {
if (attempt < maxRetries - 1) {
console.log(`Timeout. Retry ${attempt + 1}/${maxRetries}`);
return null;
}
return this.handleServiceUnavailable('timeout');
}

if (error instanceof ConnectionError) {
if (attempt < maxRetries - 1) {
console.log(`Connection error. Retry ${attempt + 1}/${maxRetries}`);
return null;
}
return this.handleServiceUnavailable('connection');
}

if (error instanceof CircuitBreakerOpenError) {
return this.handleServiceUnavailable('circuit_open');
}

// Unknown error
return {
status: 'error',
errorType: 'unexpected',
message: error.message,
recoverable: false
};
}

private handleServiceUnavailable(reason: string): ActionResult {
if (this.failOpen) {
console.warn(`ASCEND unavailable (${reason}). Allowing (fail-open).`);
return {
status: 'success',
data: { failOpen: true },
message: 'Allowed due to service unavailability'
};
}

return {
status: 'blocked',
errorType: 'service_unavailable',
message: 'Governance service unavailable',
recoverable: true
};
}

private sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}

// Usage
const agent = new ResilientAgent(process.env.ASCEND_API_KEY!);
const result = await agent.governedAction(
'database.query',
'production_db',
{ query: 'SELECT * FROM users' }
);
console.log(result);

Metrics and Monitoring

Collect and export metrics.

/**
* Metrics and monitoring integration
*/
import { AscendClient, Decision } from '@ascend/sdk';

interface MetricData {
name: string;
value: number;
tags: Record<string, string>;
timestamp: Date;
}

class MonitoredAgent {
private client: AscendClient;
private metrics: MetricData[] = [];

constructor(apiKey: string) {
this.client = new AscendClient({
apiKey,
agentId: 'monitored-agent',
agentName: 'Monitored Agent',
enableMetrics: true
});
}

async evaluateWithMetrics(
actionType: string,
resource: string,
parameters: Record<string, unknown>
): Promise<void> {
const startTime = Date.now();

try {
const decision = await this.client.evaluateAction({
actionType,
resource,
parameters
});

const duration = Date.now() - startTime;

// Record latency
this.recordMetric('ascend.latency', duration, {
actionType,
resource,
decision: decision.decision
});

// Record decision
this.recordMetric('ascend.decision', 1, {
actionType,
resource,
decision: decision.decision,
riskLevel: this.getRiskLevel(decision.riskScore)
});

} catch (error) {
const duration = Date.now() - startTime;

this.recordMetric('ascend.error', 1, {
actionType,
resource,
errorType: (error as Error).constructor.name
});

this.recordMetric('ascend.latency', duration, {
actionType,
resource,
decision: 'error'
});

throw error;
}
}

private recordMetric(
name: string,
value: number,
tags: Record<string, string>
): void {
this.metrics.push({
name,
value,
tags,
timestamp: new Date()
});

// Export to monitoring system
this.exportToDatadog(name, value, tags);
}

private exportToDatadog(
name: string,
value: number,
tags: Record<string, string>
): void {
// Integration with Datadog
console.log(`[Datadog] ${name}: ${value}`, tags);
}

private getRiskLevel(score?: number): string {
if (!score) return 'unknown';
if (score < 45) return 'low';
if (score < 70) return 'medium';
if (score < 85) return 'high';
return 'critical';
}

getMetricsSnapshot() {
const snapshot = this.client.getMetrics();
return {
...snapshot,
customMetrics: this.metrics
};
}
}

Testing Patterns

Test your agents without hitting production.

/**
* Testing patterns for ASCEND SDK
*/
import { AscendClient, Decision, EvaluateActionResult } from '@ascend/sdk';

// Mock client for unit tests
class MockAscendClient {
private defaultDecision: Decision;
public calls: Array<Record<string, unknown>> = [];

constructor(defaultDecision: Decision = Decision.ALLOWED) {
this.defaultDecision = defaultDecision;
}

async evaluateAction(options: Record<string, unknown>): Promise<EvaluateActionResult> {
this.calls.push(options);

return {
decision: this.defaultDecision,
actionId: `test_action_${this.calls.length}`,
reason: 'Test decision',
riskScore: 25,
policyViolations: [],
conditions: [],
requiredApprovers: [],
metadata: {}
};
}

async logActionCompleted(actionId: string, result?: unknown): Promise<void> {
this.calls.push({ type: 'completed', actionId, result });
}

async logActionFailed(actionId: string, error: Error): Promise<void> {
this.calls.push({ type: 'failed', actionId, error: error.message });
}

setDecision(decision: Decision): void {
this.defaultDecision = decision;
}
}

// Jest tests
describe('MyAgent', () => {
let mockClient: MockAscendClient;

beforeEach(() => {
mockClient = new MockAscendClient();
});

test('executes allowed actions', async () => {
const agent = new MyAgent(mockClient as unknown as AscendClient);

const result = await agent.doSomething();

expect(result.status).toBe('success');
expect(mockClient.calls.length).toBeGreaterThan(0);
});

test('handles denied actions', async () => {
mockClient.setDecision(Decision.DENIED);
const agent = new MyAgent(mockClient as unknown as AscendClient);

const result = await agent.doSomething();

expect(result.status).toBe('denied');
});

test('sends correct parameters', async () => {
const agent = new MyAgent(mockClient as unknown as AscendClient);

await agent.queryDatabase('SELECT * FROM users');

const call = mockClient.calls[0];
expect(call.actionType).toBe('database.query');
expect((call.parameters as Record<string, unknown>).query).toContain('SELECT');
});
});

// Integration tests with staging
describe('Integration Tests', () => {
let client: AscendClient;

beforeAll(() => {
client = new AscendClient({
apiKey: process.env.ASCEND_STAGING_API_KEY!,
agentId: 'test-agent',
agentName: 'Integration Test Agent',
apiUrl: 'https://staging.owkai.app'
});
});

test('connects to staging', async () => {
const status = await client.testConnection();
expect(status.status).toBe('connected');
});

test('evaluates low-risk action', async () => {
const decision = await client.evaluateAction({
actionType: 'database.query',
resource: 'test_db',
parameters: { query: 'SELECT 1' }
});

expect(decision.decision).toBe(Decision.ALLOWED);
});
});

// Placeholder class
class MyAgent {
constructor(private client: AscendClient) {}

async doSomething(): Promise<{ status: string }> {
const decision = await this.client.evaluateAction({
actionType: 'test.action',
resource: 'test',
parameters: {}
});

return { status: decision.decision === Decision.ALLOWED ? 'success' : 'denied' };
}

async queryDatabase(query: string): Promise<unknown> {
await this.client.evaluateAction({
actionType: 'database.query',
resource: 'production_db',
parameters: { query }
});
return [];
}
}

See Also