Skip to content

JavaScript SDK

Start here

Goal Guide
Run a workflow Run your first workflow
Write a worker Write your first worker
Build an agent Run your first agent
Category Maintained upstream example
Workflow and worker Examples
Agentic workflow Agentic workflow examples
API journey API journey examples

The agentic-workflow row covers SDK examples that orchestrate LLMs or tools. It is separate from the SDK-authored Conductor Agent quickstart, which is available above.

Connect to Conductor

For local OSS, set CONDUCTOR_SERVER_URL=http://localhost:8080/api.

For Orkes Developer Edition, set CONDUCTOR_SERVER_URL=https://developer.orkescloud.com/api, CONDUCTOR_AUTH_KEY, and CONDUCTOR_AUTH_SECRET. Keep credentials out of source control.

This SDK reads these environment variables when constructing its standard client configuration.

Install the SDK

Maintenance

This SDK is part of the Conductor OSS ecosystem. Conductor OSS remains actively maintained under the Conductor OSS community, with Orkes contributing maintenance, engineering, documentation, and enterprise support.

npm install @io-orkes/conductor-javascript

60-Second Quickstart

Step 1: Create a workflow

Workflows are definitions that reference task types. We'll build a workflow called greetings that runs one worker task and returns its output.

import { ConductorWorkflow, simpleTask } from "@io-orkes/conductor-javascript";

const workflow = new ConductorWorkflow(executor, "greetings")
  .add(simpleTask("greet_ref", "greet", { name: "${workflow.input.name}" }))
  .outputParameters({ result: "${greet_ref.output.result}" });

await workflow.register();

Step 2: Write a worker

Workers are TypeScript functions decorated with @worker that poll Conductor for tasks and execute them.

import { worker } from "@io-orkes/conductor-javascript";

@worker({ taskDefName: "greet" })
async function greet(task: Task) {
  return {
    status: "COMPLETED",
    outputData: { result: `Hello ${task.inputData.name}` },
  };
}

Step 3: Run your first workflow app

Create a quickstart.ts with the following:

import {
  OrkesClients,
  ConductorWorkflow,
  TaskHandler,
  worker,
  simpleTask,
} from "@io-orkes/conductor-javascript";
import type { Task } from "@io-orkes/conductor-javascript";

// A worker is any TypeScript function.
@worker({ taskDefName: "greet" })
async function greet(task: Task) {
  return {
    status: "COMPLETED" as const,
    outputData: { result: `Hello ${task.inputData.name}` },
  };
}

async function main() {
  // Configure the SDK (reads CONDUCTOR_SERVER_URL / CONDUCTOR_AUTH_* from env).
  const clients = await OrkesClients.from();
  const executor = clients.getWorkflowClient();

  // Build a workflow with the fluent builder.
  const workflow = new ConductorWorkflow(executor, "greetings")
    .add(simpleTask("greet_ref", "greet", { name: "${workflow.input.name}" }))
    .outputParameters({ result: "${greet_ref.output.result}" });

  await workflow.register();

  // Start polling for tasks (auto-discovers @worker decorated functions).
  const handler = new TaskHandler({
    client: clients.getClient(),
    scanForDecorated: true,
  });
  await handler.startWorkers();

  // Run the workflow and get the result.
  const run = await workflow.execute({ name: "Conductor" });
  console.log(`result: ${run.output?.result}`);

  await handler.stopWorkers();
}

main();

Run it:

npx ts-node quickstart.ts

That's it — you defined a worker, built a workflow, and executed it. Open the UI for the Conductor server you configured to inspect the execution.

What You Can Build

The SDK provides typed builders for common orchestration patterns. Here's a taste of what you can wire together:

HTTP calls from workflows — call any API without writing a worker (kitchensink.ts):

httpTask("call_api", {
  uri: "https://api.example.com/orders/${workflow.input.orderId}",
  method: "POST",
  body: { items: "${workflow.input.items}" },
  headers: { "Authorization": "Bearer ${workflow.input.token}" },
})

Wait between tasks — pause a workflow for a duration or until a timestamp (kitchensink.ts):

.add(simpleTask("step1_ref", "process_order", {...}))
.add(waitTaskDuration("cool_down", "10s"))        // wait 10 seconds
.add(simpleTask("step2_ref", "send_confirmation", {...}))

Parallel execution (fork/join) — fan out to multiple branches and join (fork-join.ts):

workflow.fork([
  [simpleTask("email_ref", "send_email", {})],
  [simpleTask("sms_ref", "send_sms", {})],
  [simpleTask("push_ref", "send_push", {})],
])

Conditional branching — route based on input values (kitchensink.ts):

switchTask("route_ref", "${workflow.input.tier}", {
  premium: [simpleTask("fast_ref", "fast_track", {})],
  standard: [simpleTask("normal_ref", "standard_process", {})],
})

Sub-workflows — compose workflows from smaller workflows (sub-workflows.ts):

const child = new ConductorWorkflow(executor, "payment_flow").add(...);
const parent = new ConductorWorkflow(executor, "order_flow")
  .add(child.toSubWorkflowTask("pay_ref"));

All of these are type-safe, composable, and registered to the server as JSON — workers can be in any language.

Workers

Workers are TypeScript functions that execute Conductor tasks. Decorate any function with @worker to register it as a worker (auto-discovered by TaskHandler) and use it as a workflow task.

import { worker, TaskHandler } from "@io-orkes/conductor-javascript";

@worker({ taskDefName: "greet", concurrency: 5, pollInterval: 100 })
async function greet(task: Task) {
  return {
    status: "COMPLETED",
    outputData: { result: `Hello ${task.inputData.name}` },
  };
}

@worker({ taskDefName: "process_payment", domain: "payments" })
async function processPayment(task: Task) {
  const result = await paymentGateway.charge(task.inputData.customerId, task.inputData.amount);
  return { status: "COMPLETED", outputData: { transactionId: result.id } };
}

// Auto-discover and start all decorated workers
const handler = new TaskHandler({ client, scanForDecorated: true });
await handler.startWorkers();

// Graceful shutdown
process.on("SIGTERM", async () => {
  await handler.stopWorkers();
  process.exit(0);
});

Worker configuration:

@worker({
  taskDefName: "my_task",    // Required: task name
  concurrency: 5,             // Max concurrent tasks (default: 1)
  pollInterval: 100,          // Polling interval in ms (default: 100)
  domain: "production",       // Task domain for multi-tenancy
  workerId: "worker-123",     // Unique worker identifier
})

Environment variable overrides (no code changes needed):

# Global (all workers)
export CONDUCTOR_WORKER_ALL_POLL_INTERVAL=500
export CONDUCTOR_WORKER_ALL_CONCURRENCY=10

# Per-worker override
export CONDUCTOR_WORKER_SEND_EMAIL_CONCURRENCY=20
export CONDUCTOR_WORKER_PROCESS_PAYMENT_DOMAIN=payments

NonRetryableException — mark failures as terminal to prevent retries:

import { NonRetryableException } from "@io-orkes/conductor-javascript";

@worker({ taskDefName: "validate_order" })
async function validateOrder(task: Task) {
  const order = await getOrder(task.inputData.orderId);
  if (!order) {
    throw new NonRetryableException("Order not found"); // FAILED_WITH_TERMINAL_ERROR
  }
  return { status: "COMPLETED", outputData: { validated: true } };
}
  • throw new Error() → Task status: FAILED (will retry)
  • throw new NonRetryableException() → Task status: FAILED_WITH_TERMINAL_ERROR (no retry)

Long-running tasks with TaskContext — return IN_PROGRESS to keep a task alive while an external process completes. Conductor will call back after the specified interval (task-context.ts):

import { worker, getTaskContext } from "@io-orkes/conductor-javascript";

@worker({ taskDefName: "process_video" })
async function processVideo(task: Task) {
  const ctx = getTaskContext();
  ctx?.addLog("Starting video processing...");

  if (!isComplete(task.inputData)) {
    ctx?.setCallbackAfter(30); // check again in 30 seconds
    return { status: "IN_PROGRESS", callbackAfterSeconds: 30 };
  }

  return { status: "COMPLETED", outputData: { url: "..." } };
}

TaskContext is also available for one-shot workers — use ctx?.addLog() to stream logs visible in the Conductor UI.

Event listeners for observability:

const handler = new TaskHandler({
  client,
  scanForDecorated: true,
  eventListeners: [{
    onTaskExecutionCompleted(event) {
      metrics.histogram("task_duration_ms", event.durationMs, { task_type: event.taskType });
    },
    onTaskUpdateFailure(event) {
      alertOps({ severity: "CRITICAL", message: `Task update failed`, taskId: event.taskId });
    },
  }],
});

Organize workers across files with module imports:

const handler = await TaskHandler.create({
  client,
  importModules: ["./workers/orderWorkers", "./workers/paymentWorkers"],
});
await handler.startWorkers();

Legacy TaskManager API continues to work with full backward compatibility. New projects should use @worker + TaskHandler above.

Monitoring Workers

Enable Prometheus metrics with the built-in MetricsCollector:

import { MetricsCollector, MetricsServer, TaskHandler } from "@io-orkes/conductor-javascript";

const metrics = new MetricsCollector();
const server = new MetricsServer(metrics, 9090);
await server.start();

const handler = new TaskHandler({
  client,
  eventListeners: [metrics],
  scanForDecorated: true,
});
await handler.startWorkers();
// GET http://localhost:9090/metrics — Prometheus text format
// GET http://localhost:9090/health  — {"status":"UP"}

Collects 18 metric types: poll counts, execution durations, error rates, output sizes, and more — with p50/p75/p90/p95/p99 quantiles. See METRICS.md for the full reference.

Managing Workflow Executions

Once a workflow is registered (see What You Can Build), you can run and manage it through the full lifecycle:

const executor = clients.getWorkflowClient();

// Start (async — returns immediately)
const workflowId = await executor.startWorkflow({
  name: "order_flow",
  input: { orderId: "ORDER-123" },
});

// Execute (sync — waits for completion)
const result = await workflow.execute({ orderId: "123" });

// Lifecycle management
await executor.pause(workflowId);
await executor.resume(workflowId);
await executor.terminate(workflowId, "cancelled by user");
await executor.restart(workflowId);
await executor.retry(workflowId);

// Signal a running WAIT task
await executor.signal(workflowId, TaskResultStatusEnum.COMPLETED, { approved: true });

// Search workflows
const results = await executor.search("workflowType = 'order_flow' AND status = 'RUNNING'");

See workflow-ops.ts for a runnable example covering all lifecycle operations.

Troubleshooting

  • Worker stops polling or crashes: TaskHandler monitors and restarts worker polling loops by default. Expose a health check using handler.running and handler.runningWorkerCount. If you enable metrics, alert on worker_restart_total.
  • HTTP/2 connection errors: The SDK uses Undici for HTTP/2 when available. If your environment has unstable long-lived connections, the SDK falls back to HTTP/1.1 automatically. You can also provide a custom fetch function: orkesConductorClient(config, myFetch).
  • Task stuck in SCHEDULED: Ensure your worker is polling for the correct taskDefName. Workers must be started before the workflow is executed.

Examples

See the Examples Guide for the full catalog. Key examples:

Example Description Run
workers-e2e.ts End-to-end: 3 chained workers with verification npx ts-node examples/workers-e2e.ts
quickstart.ts 60-second intro: @worker + workflow + execute npx ts-node examples/quickstart.ts
kitchensink.ts All major task types in one workflow npx ts-node examples/kitchensink.ts
workflow-ops.ts Lifecycle: pause, resume, terminate, retry, search npx ts-node examples/workflow-ops.ts
test-workflows.ts Unit testing with mock outputs (no workers) npx ts-node examples/test-workflows.ts
metrics.ts Prometheus metrics + HTTP server on :9090 npx ts-node examples/metrics.ts
express-worker-service.ts Express.js + workers in one process npx ts-node examples/express-worker-service.ts
function-calling.ts LLM dynamically picks which worker to call npx ts-node examples/agentic-workflows/function-calling.ts
fork-join.ts Parallel branches with join synchronization npx ts-node examples/advanced/fork-join.ts
sub-workflows.ts Workflow composition with sub-workflows npx ts-node examples/advanced/sub-workflows.ts
human-tasks.ts Human-in-the-loop: claim, update, complete npx ts-node examples/advanced/human-tasks.ts

API Journey Examples

End-to-end examples covering all APIs for each domain:

Example APIs Run
authorization.ts Authorization APIs (17 calls) npx ts-node examples/api-journeys/authorization.ts
metadata.ts Metadata APIs (21 calls) npx ts-node examples/api-journeys/metadata.ts
prompts.ts Prompt APIs (9 calls) npx ts-node examples/api-journeys/prompts.ts
schedules.ts Schedule APIs (13 calls) npx ts-node examples/api-journeys/schedules.ts
secrets.ts Secret APIs (12 calls) npx ts-node examples/api-journeys/secrets.ts
integrations.ts Integration APIs (22 calls) npx ts-node examples/api-journeys/integrations.ts
schemas.ts Schema APIs (10 calls) npx ts-node examples/api-journeys/schemas.ts
applications.ts Application APIs (20 calls) npx ts-node examples/api-journeys/applications.ts
event-handlers.ts Event Handler APIs (18 calls) npx ts-node examples/api-journeys/event-handlers.ts

AI & LLM Workflows

Conductor supports AI-native workflows including agentic tool calling, RAG pipelines, and multi-agent orchestration. The SDK provides typed builders for all LLM task types:

Builder Description
llmChatCompleteTask LLM chat completion (OpenAI, Anthropic, etc.)
llmTextCompleteTask Text completion
llmGenerateEmbeddingsTask Generate vector embeddings
llmIndexDocumentTask Index a document into a vector store
llmIndexTextTask Index text into a vector store
llmSearchIndexTask Search a vector index
llmSearchEmbeddingsTask Search by embedding similarity
llmStoreEmbeddingsTask Store pre-computed embeddings
llmQueryEmbeddingsTask Query embeddings
generateImageTask Generate images
generateAudioTask Generate audio
callMcpToolTask Call an MCP tool
listMcpToolsTask List available MCP tools

Example: LLM chat workflow

import { ConductorWorkflow, llmChatCompleteTask, Role } from "@io-orkes/conductor-javascript";

const workflow = new ConductorWorkflow(executor, "ai_chat")
  .add(llmChatCompleteTask("chat_ref", "openai", "gpt-4o", {
    messages: [{ role: Role.USER, message: "${workflow.input.question}" }],
    temperature: 0.7,
    maxTokens: 500,
  }))
  .outputParameters({ answer: "${chat_ref.output.result}" });

await workflow.register();
const run = await workflow.execute({ question: "What is Conductor?" });
console.log(run.output?.answer);

Agentic Workflows

Build AI agents where LLMs dynamically select and call TypeScript workers as tools. See examples/agentic-workflows/ for all examples.

Example Description
llm-chat.ts Automated multi-turn conversation between two LLMs
llm-chat-human-in-loop.ts Interactive chat with WAIT tasks for human input
function-calling.ts LLM dynamically picks which worker function to call
mcp-weather-agent.ts MCP tool discovery and invocation for real-time data
multiagent-chat.ts Multi-agent debate: optimist vs skeptic with moderator

RAG and Vector DB Workflows

Example Description
rag-workflow.ts End-to-end RAG: document indexing → semantic search → LLM answer
vector-db.ts Vector DB operations: embedding generation, storage, search

Documentation

Document Description
SDK Development Guide Architecture, patterns, pitfalls, testing
Metrics Reference All 18 Prometheus metrics with descriptions
Breaking Changes v3.x migration guide
Workflow Management Start, pause, resume, terminate, retry, search, signal
Task Management Task operations, logs, queue management
Metadata Task & workflow definitions, tags, rate limits
Scheduling Workflow scheduling with CRON expressions
Applications Application management, access keys, roles
Events Event handlers, event-driven workflows
Human Tasks Human-in-the-loop workflows, form templates
Service Registry Service discovery, circuit breakers

Support

License

Apache 2.0