@runctl/sdk
v0.1.8
Published
The official TypeScript/JavaScript SDK for Runctl
Maintainers
Readme
@runctl/sdk
The official TypeScript/JavaScript SDK for Runctl — a multi-tenant runtime and scheduler for AI agents, built for production, not prototypes.
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
npm install @runctl/sdk
# or
yarn add @runctl/sdk
# or
pnpm add @runctl/sdkQuick Start
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
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
// 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
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.
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. Tenant settings (e.g. skillAllowlist, customToolUrlPrefixes) may restrict which tools and URLs are allowed.
List Agents
// 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
// 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
// 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
// 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
// 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
// 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.
// 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
// 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
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
// 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
// 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
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
// 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
// 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
// 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
// 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
// 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
// Get preferences
const prefs = await client.tenants.getNotificationPreferences(tenantId);
// Update preferences
await client.tenants.updateNotificationPreferences(tenantId, {
emailEnabled: true,
emailRecipients: ['[email protected]'],
thresholds: [50, 80, 100],
webhookEnabled: true,
webhookUrl: 'https://example.com/webhooks/billing',
});Error Handling
The SDK provides typed errors for different failure scenarios:
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:
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
const client = new Runctl({
apiKey: 'your-api-key',
headers: {
'X-Request-ID': 'custom-request-id',
'X-Correlation-ID': 'trace-123',
},
});Polling with Exponential Backoff
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
// 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.
