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 |
Featured examples
| 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.
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:
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:
TaskHandlermonitors and restarts worker polling loops by default. Expose a health check usinghandler.runningandhandler.runningWorkerCount. If you enable metrics, alert onworker_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
- Open an issue (SDK) for SDK bugs, questions, and feature requests
- Open an issue (Conductor server) for Conductor OSS server issues
- Join the Conductor Slack for community discussion and help
- Orkes Community Forum for Q&A
License
Apache 2.0