npm package: @runctl/sdk
Description: The official TypeScript/JavaScript SDK for Runctl
Latest version: 0.1.8

# @runctl/sdk

The official TypeScript/JavaScript SDK for [Runctl](https://runctl.com) — a multi-tenant runtime and scheduler for AI agents, built for production, not prototypes.

[![npm version](https://badge.fury.io/js/@runctl%2Fsdk.svg)](https://www.npmjs.com/package/@runctl/sdk)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/)

## Features

- 🤖 **Agent Management** — Create, deploy, scale, and monitor AI agents
- 📋 **Task Orchestration** — Submit tasks, batch operations, and track execution
- 🔍 **Trace Analysis & Forking** — Inspect execution steps, fork tasks from any point, and compare task divergences
- 👥 **Team Coordination** — Organize agents into teams for collaborative workflows
- 🔄 **Workflow Automation** — Define and execute multi-step agent workflows
- 📊 **Usage & Billing** — Monitor usage, costs, and manage billing
- 📈 **Self-Improvement Analytics** — Track agent performance, approval rates, and optimization metrics
- 🔒 **Type-Safe** — Full TypeScript support with comprehensive type definitions
- ⚡ **Automatic Retries** — Built-in retry logic with exponential backoff
- 🌐 **Environment Support** — Works in Node.js, Deno, and edge runtimes

## Installation

```bash
npm install @runctl/sdk
# or
yarn add @runctl/sdk
# or
pnpm add @runctl/sdk
```

## Quick Start

```typescript
import { Runctl } from '@runctl/sdk';

// Initialize the client
const client = new Runctl({
  apiKey: process.env.RUNCTL_API_KEY!,
});

// Create an agent
const agent = await client.agents.create({
  name: 'Research Assistant',
  type: 'reactive',
  modelProvider: 'openai',
  model: 'gpt-4o-mini',
  systemPrompt: 'You are a helpful research assistant.',
});

// Deploy the agent
await client.agents.deploy(agent.id, { replicas: 1 });

// Create a task
const task = await client.tasks.create({
  agentId: agent.id,
  input: {
    version: '1.0',
    payload: { query: 'What are the latest trends in AI?' },
  },
});

// Wait for completion
const completedTask = await client.tasks.waitForCompletion(task.id);
console.log('Result:', completedTask.result);
```

## Configuration

### Direct Configuration

```typescript
const client = new Runctl({
  apiKey: 'your-api-key', // Required
  baseUrl: 'https://api.runctl.com', // Optional, defaults to production
  timeout: 30000, // Request timeout in ms (default: 30000)
  retries: 3, // Max retry attempts (default: 3)
  headers: {
    // Custom headers
    'X-Custom-Header': 'value',
  },
});
```

### Environment Variables

```typescript
// Automatically reads from environment variables
const client = Runctl.fromEnv();
```

Supported environment variables:

| Variable          | Description             | Default                  |
| ----------------- | ----------------------- | ------------------------ |
| `RUNCTL_API_KEY`  | Your API key (required) | —                        |
| `RUNCTL_BASE_URL` | API base URL            | `https://api.runctl.com` |
| `RUNCTL_TIMEOUT`  | Request timeout in ms   | `30000`                  |
| `RUNCTL_RETRIES`  | Max retry attempts      | `3`                      |

## Agents

Agents are the core building blocks of Runctl. They represent AI-powered workers that can execute tasks.

### Create an Agent

```typescript
const agent = await client.agents.create({
  name: 'Code Reviewer',
  type: 'reactive',
  modelProvider: 'openai',
  model: 'gpt-4o',
  systemPrompt:
    'You are an expert code reviewer. Analyze code for bugs, security issues, and best practices.',
  tools: ['code_analysis', 'security_scan'],
  resources: {
    memoryMB: 512,
    cpuUnits: 256,
  },
});
```

### Custom tools

You can attach **custom tools** to an agent by passing full `AgentTool` objects in `tools` (instead of built-in tool names). Custom tools are invoked by the runtime when the agent calls them: the runtime POSTs the validated arguments to your endpoint and returns the response.

Each custom tool must have exactly one execution target:

- **`invokeUrl`** — HTTPS URL; the runtime POSTs the tool arguments as JSON (supported today).
- **`lambdaArn`** — AWS Lambda ARN (when supported by the runtime).
- **`inlineScript`** — Inline script execution (when supported by the runtime).

Use the same `client.agents.create()` and `client.agents.update()` APIs; no extra SDK endpoints are required. The `AgentTool` type is exported from the SDK.

```typescript
import type { AgentTool } from '@runctl/sdk';

const customTool: AgentTool = {
  id: 'tool_1',
  name: 'my_api_tool',
  description: 'Calls my external API',
  inputSchema: {
    type: 'object',
    properties: { query: { type: 'string' } },
    required: ['query'],
  },
  enabled: true,
  invokeUrl: 'https://api.example.com/invoke',
};

const agent = await client.agents.create({
  name: 'Agent with custom tool',
  type: 'reactive',
  modelProvider: 'openai',
  model: 'gpt-4o-mini',
  systemPrompt: 'You are a helpful assistant.',
  tools: [customTool],
});
```

Custom tool names must not conflict with [built-in tool names](https://docs.runctl.com/guides/tools). Tenant settings (e.g. `skillAllowlist`, `customToolUrlPrefixes`) may restrict which tools and URLs are allowed.

### List Agents

```typescript
// List all agents
const { data: agents, pagination } = await client.agents.list();

// With filters
const { data: runningAgents } = await client.agents.list({
  status: 'running',
  type: 'reactive',
  limit: 10,
  page: 1,
});
```

### Deploy & Scale

```typescript
// Deploy an agent
const deployment = await client.agents.deploy(agentId, {
  replicas: 2,
  strategy: 'rolling',
});

// Scale up/down
const scaleResult = await client.agents.scale(agentId, {
  desiredReplicas: 5,
  strategy: 'immediate',
});

// Stop an agent
await client.agents.stop(agentId);

// Restart
await client.agents.restart(agentId);
```

### Monitor Agents

```typescript
// Get agent metrics
const metrics = await client.agents.getMetrics(agentId, {
  period: 'day',
});

// Get agent logs
const { logs, hasMore } = await client.agents.getLogs(agentId, {
  level: 'error',
  limit: 100,
});

// List instances
const { data: instances } = await client.agents.listInstances(agentId);

// Get instance health
const health = await client.agents.getInstanceHealth(agentId, instanceId);
```

## Tasks

Tasks represent units of work assigned to agents.

### Create Tasks

```typescript
// Single task
const task = await client.tasks.create({
  agentId: 'agt_123',
  input: {
    version: '1.0',
    payload: { message: 'Analyze this code', code: '...' },
  },
  priority: 'high',
});

// Batch create (up to 25 tasks)
const tasks = await client.tasks.createBatch({
  tasks: [
    { agentId: 'agt_123', input: { version: '1.0', payload: { query: 'Query 1' } } },
    { agentId: 'agt_123', input: { version: '1.0', payload: { query: 'Query 2' } } },
  ],
});
```

### Wait for Completion

```typescript
// Wait with default options (5 minute timeout)
const completed = await client.tasks.waitForCompletion(taskId);

// With progress updates
const completed = await client.tasks.waitForCompletion(taskId, {
  pollIntervalMs: 2000,
  timeoutMs: 600000, // 10 minutes
  onPoll: task => {
    console.log(`Status: ${task.status}`);
  },
});
```

### List & Filter Tasks

```typescript
// List with filters
const { data: tasks } = await client.tasks.list({
  status: 'completed',
  agentId: 'agt_123',
  fromDate: new Date('2026-01-01'),
  toDate: new Date(),
});

// Iterate over all matching tasks (auto-pagination)
for await (const task of client.tasks.listAll({ status: 'failed' })) {
  console.log(`Failed task: ${task.id}`);
}
```

### Fork Tasks

Fork a new task from a specific execution step. This is useful for "What-if" debugging or re-running from a specific state.

```typescript
// Fork from step 2 of an existing trace
const forkedTask = await client.tasks.fork('tsk_abc123', 2);

// Fork with a custom modified snapshot (What-if scenario)
const whatIfTask = await client.tasks.fork('tsk_abc123', 2, {
  prompt: 'A slightly different prompt to test...',
  memory: {
    customValue: 'something new',
  },
});
```

### Task Statistics

```typescript
// Get overall stats
const stats = await client.tasks.getStats();
console.log(`Total tasks: ${stats.total}`);
console.log(`By status:`, stats.byStatus);

// Get execution history
const history = await client.tasks.getExecutionHistory('7d');
```

## Teams

Teams allow you to coordinate multiple agents for collaborative tasks.

### Create a Team

```typescript
const team = await client.teams.create({
  name: 'Research Team',
  description: 'A team for research and analysis tasks',
  members: [
    { agentId: 'agt_researcher', role: 'researcher' },
    { agentId: 'agt_analyzer', role: 'analyzer' },
    { agentId: 'agt_writer', role: 'writer' },
  ],
});
```

### Manage Team Members

```typescript
// Add a member
await client.teams.addMember(teamId, {
  agentId: 'agt_reviewer',
  role: 'reviewer',
});

// Remove a member
await client.teams.removeMember(teamId, 'agt_reviewer');

// List members
const members = await client.teams.listMembers(teamId);
```

### Deploy Teams

```typescript
// Deploy all team members
await client.teams.deploy(teamId);

// Stop all team members
await client.teams.stop(teamId);

// Get team metrics
const metrics = await client.teams.getMetrics(teamId);
```

## Workflows

Workflows define multi-step processes executed by agents.

### Create a Workflow Definition

```typescript
const definition = await client.workflowDefinitions.create({
  name: 'Content Pipeline',
  description: 'Research, write, and review content',
  pattern: 'sequential',
  steps: [
    {
      stepId: 'research',
      name: 'Research Topic',
      agentId: 'agt_researcher',
    },
    {
      stepId: 'write',
      name: 'Write Draft',
      agentId: 'agt_writer',
      dependsOn: ['research'],
    },
    {
      stepId: 'review',
      name: 'Review & Edit',
      agentId: 'agt_editor',
      dependsOn: ['write'],
    },
  ],
});
```

### Execute Workflows

```typescript
// Start a workflow
const workflow = await client.workflows.execute({
  workflowDefinitionId: 'wfd_123',
  input: { topic: 'AI in Healthcare' },
});

// Execute and wait for completion
const completed = await client.workflows.executeAndWait(
  {
    workflowDefinitionId: 'wfd_123',
    input: { topic: 'AI in Healthcare' },
  },
  {
    timeoutMs: 600000,
    onUpdate: state => {
      console.log(`Step: ${state.currentStepId}, Status: ${state.status}`);
    },
  }
);

// Check workflow status
if (client.workflows.isCompleted(completed)) {
  console.log('Output:', completed.output);
} else if (client.workflows.isFailed(completed)) {
  console.log('Error:', completed.error);
}
```

### Monitor Workflows

```typescript
// Get workflow details
const workflow = await client.workflows.getById('wfl_123');

// Get step details
const steps = await client.workflows.getSteps('wfl_123');
steps.forEach(step => {
  console.log(`${step.stepId}: ${step.status}`);
});

// Get workflow metrics
const metrics = await client.workflows.getMetrics('wfl_123');
console.log(`Duration: ${metrics.durationSeconds}s`);
console.log(`Completed: ${metrics.stepCounts.completed}/${metrics.stepCounts.total}`);

// Cancel a running workflow
await client.workflows.cancel('wfl_123');
```

## Tenants

Manage your tenant account, usage, billing, and team members.

### Usage & Metrics

```typescript
// Get current usage
const usage = await client.tenants.getUsage(tenantId);
console.log(`Active agents: ${usage.activeAgents}`);
console.log(`Tasks today: ${usage.tasksToday}`);

// Get usage history
const history = await client.tenants.getUsageHistory(tenantId, { period: '30d' });

// Get token usage history
const tokens = await client.tenants.getTokenUsageHistory(tenantId, { period: '7d' });

// Get cost history
const costs = await client.tenants.getCostHistory(tenantId, { period: '30d' });
```

### Billing

```typescript
// Get billing summary
const billing = await client.tenants.getBillingSummary(tenantId);
console.log(`Current spend: $${billing.currentSpend}`);
console.log(`Period: ${billing.periodStart} to ${billing.periodEnd}`);

// Create a billing portal session
const portal = await client.tenants.createPortalSession(tenantId, {
  returnUrl: 'https://app.example.com/billing',
});
// Redirect user to portal.portalUrl

// Create a checkout session for payment setup
const checkout = await client.tenants.createCheckoutSession(tenantId, {
  returnUrl: 'https://app.example.com/success',
  cancelUrl: 'https://app.example.com/cancel',
});
```

### Team Members

```typescript
// List members
const members = await client.tenants.listMembers(tenantId);

// Update member role
await client.tenants.updateMemberRole(tenantId, userId, {
  role: 'admin',
});

// Remove member
await client.tenants.removeMember(tenantId, userId);

// Create invite code
const invite = await client.tenants.createInviteCode(tenantId, {
  role: 'member',
  expiresInDays: 30,
  maxUses: 10,
});
console.log(`Invite code: ${invite.inviteCode}`);

// List and revoke invite codes
const codes = await client.tenants.listInviteCodes(tenantId);
await client.tenants.revokeInviteCode(tenantId, 'ABC123XYZ');
```

### Notification Preferences

```typescript
// Get preferences
const prefs = await client.tenants.getNotificationPreferences(tenantId);

// Update preferences
await client.tenants.updateNotificationPreferences(tenantId, {
  emailEnabled: true,
  emailRecipients: ['alerts@example.com'],
  thresholds: [50, 80, 100],
  webhookEnabled: true,
  webhookUrl: 'https://example.com/webhooks/billing',
});
```

## Error Handling

The SDK provides typed errors for different failure scenarios:

```typescript
import {
  RunctlError,
  RunctlAuthenticationError,
  RunctlAuthorizationError,
  RunctlValidationError,
  RunctlNotFoundError,
  RunctlRateLimitError,
  RunctlQuotaExceededError,
  RunctlServerError,
  RunctlNetworkError,
  isRetryableError,
} from '@runctl/sdk';

try {
  const agent = await client.agents.getById('agt_invalid');
} catch (error) {
  if (error instanceof RunctlNotFoundError) {
    console.log(`Agent not found: ${error.resourceId}`);
  } else if (error instanceof RunctlAuthenticationError) {
    console.log('Invalid API key');
  } else if (error instanceof RunctlRateLimitError) {
    console.log(`Rate limited. Retry after ${error.retryAfter} seconds`);
  } else if (error instanceof RunctlValidationError) {
    console.log('Validation error:', error.details);
  } else if (error instanceof RunctlQuotaExceededError) {
    console.log(`Quota exceeded: ${error.resource}`);
  } else if (isRetryableError(error)) {
    console.log('Transient error, safe to retry');
  }
}
```

### Error Types

| Error                       | HTTP Code | Description                |
| --------------------------- | --------- | -------------------------- |
| `RunctlAuthenticationError` | 401       | Invalid or missing API key |
| `RunctlAuthorizationError`  | 403       | Insufficient permissions   |
| `RunctlValidationError`     | 400       | Invalid request parameters |
| `RunctlNotFoundError`       | 404       | Resource not found         |
| `RunctlConflictError`       | 409       | Resource state conflict    |
| `RunctlRateLimitError`      | 429       | Rate limit exceeded        |
| `RunctlQuotaExceededError`  | 402       | Usage quota exceeded       |
| `RunctlServerError`         | 5xx       | Server-side error          |
| `RunctlNetworkError`        | —         | Network/connection error   |

## TypeScript Support

The SDK is written in TypeScript and provides comprehensive type definitions:

```typescript
import type {
  Agent,
  Task,
  AgentTeam,
  WorkflowState,
  WorkflowDefinition,
  Tenant,
  AgentType,
  TaskStatus,
  WorkflowStatus,
  CreateAgentInput,
  CreateTaskInput,
  PaginatedResponse,
} from '@runctl/sdk';

// All method parameters and return types are fully typed
const agent: Agent = await client.agents.create({
  name: 'My Agent',
  type: 'reactive',
  modelProvider: 'openai',
  model: 'gpt-4o-mini',
  systemPrompt: 'You are helpful.',
});

// Pagination types
const response: PaginatedResponse<Task> = await client.tasks.list();
```

## Advanced Usage

### Custom HTTP Headers

```typescript
const client = new Runctl({
  apiKey: 'your-api-key',
  headers: {
    'X-Request-ID': 'custom-request-id',
    'X-Correlation-ID': 'trace-123',
  },
});
```

### Polling with Exponential Backoff

```typescript
const workflow = await client.workflows.pollUntilComplete('wfl_123', {
  intervalMs: 1000, // Start with 1 second
  backoffFactor: 1.5, // Multiply by 1.5 each time
  maxIntervalMs: 30000, // Cap at 30 seconds
  timeoutMs: 600000, // 10 minute timeout
  maxRetries: 5, // Max consecutive failures
  onUpdate: state => {
    console.log(`Status: ${state.status}`);
  },
});
```

### Iterate All Resources

```typescript
// Auto-paginate through all tasks
for await (const task of client.tasks.listAll({
  status: 'completed',
  fromDate: new Date('2026-01-01'),
})) {
  // Process each task
  console.log(task.id);
}
```

## Requirements

- Node.js 20+ (or compatible runtime)
- TypeScript 5.0+ (for TypeScript users)

## License

Proprietary - All rights reserved. See your Runctl service agreement for terms of use.

## Support

- 📖 [Documentation](https://docs.runctl.com)
- 💬 [Discord Community](https://discord.gg/bxfDpsam)
- 📧 [Email Support](mailto:runctl@outlook.com)