# Understanding the Copilot SDK Factory Pattern: defineFactory, step, parallel, and pipeline Usage

> Master the Copilot SDK Factory pattern with defineFactory. Learn to build sequential steps, parallel fan-outs, and pipelines for efficient computation with JSON serializable outputs and resource limits.

- Repository: [GitHub/copilot-sdk](https://github.com/github/copilot-sdk)
- Tags: deep-dive
- Published: 2026-08-02

---

**The Copilot SDK Factory pattern enables developers to define reusable computation units using `defineFactory()`, which provides a step API for running sequential tasks, parallel fan-outs, and data-flow pipelines while enforcing JSON-serializable outputs and resource limits.**

The GitHub Copilot SDK exposes a structured Factory pattern for building composable, server-side extensions. By implementing `defineFactory()` in [`nodejs/src/factory.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/factory.ts), you create self-contained units of work that the runtime can orchestrate across distributed sessions. This architecture supports complex execution graphs through the step API, allowing extensions to parallelize independent operations or chain dependent transformations with built-in safety guards.

## Core Factory Structure

A factory definition consists of metadata describing the operation and a `run` function that receives contextual state and step utilities.

### Metadata and Limits

The `meta` object adheres to the `FactoryMeta` interface defined in [`nodejs/src/types.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/types.ts) and describes the factory's purpose and constraints:

- **description**: Human-readable summary of the factory's behavior
- **limits**: An optional `FactoryLimits` object specifying `timeoutSeconds`, `creditLimit`, and other execution boundaries

These limits are enforced by the runtime to prevent runaway execution.

### The Run Function and Context

The `run` function signature receives two arguments:

1. **`FactoryContext`**: Contains `args` (input parameters), `session` (the active session handle), and `logger` for structured logging
2. **`FactoryStep`**: An API object exposing execution methods

The implementation must return a JSON-serializable value. The SDK explicitly validates against functions, symbols, `BigInt`, `undefined`, and cyclic references, throwing a `FactoryResumeError` if the contract is violated.

## The Step API: Execution Primitives

The `step` object available in the `run` function provides three distinct execution models defined in [`nodejs/src/session.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/session.ts) (around line 145).

### Sequential Execution with step.run()

Use `step.run(thunk)` to execute a single asynchronous operation. This blocks until the thunk resolves, making it suitable for dependent operations that must complete before subsequent logic.

### Concurrent Fan-Out with step.runParallel()

**`step.runParallel(thunks[])`** executes an array of async functions concurrently. The SDK automatically evaluates all thunks and returns an array of results upon completion.

The runtime protects against resource exhaustion through `assertFactoryFanoutSize("parallel", ...)` checks in [`nodejs/src/session.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/session.ts), which validate that the number of concurrent operations does not exceed configured limits. This pattern maximizes throughput when steps are independent, such as fetching data from multiple external APIs simultaneously.

### Chained Data Flow with step.runPipeline()

**`step.runPipeline(thunks[])`** executes thunks sequentially, passing the output of each step as the input to the next. Unlike parallel execution, each function in the pipeline receives the previous step's return value.

The SDK validates pipeline depth via `assertFactoryFanoutSize("pipeline", ...)` to prevent deeply nested execution stacks. This pattern is ideal for data transformation workflows where the output of one stage feeds directly into the next.

## Execution Patterns: Parallel vs Pipeline

Understanding when to apply each execution model ensures optimal resource utilization:

- **Parallel execution** launches all thunks simultaneously and waits for every promise to settle. Use this when operations have no interdependencies and you need maximum concurrency. The SDK enforces a fan-out limit to protect the runtime from overwhelming the underlying compute resources.

- **Pipeline execution** processes thunks sequentially, creating a unidirectional data flow. Use this when each transformation depends on the previous result, such as parsing input, validating schema, and then formatting output. The SDK checks pipeline depth limits to avoid stack overflow conditions.

Both patterns share the same underlying RPC mechanism (`FactoryRunRequest`) and error handling semantics, meaning `FactoryResumeError` instances propagate identically regardless of execution strategy.

## Practical Implementation Example

The following example demonstrates defining a factory that uses both parallel and pipeline execution:

```typescript
import { defineFactory } from "@github/copilot-sdk";

const suggestionFactory = defineFactory({
  meta: {
    description: "Generate and process suggestions using parallel and pipeline stages",
    limits: { timeoutSeconds: 30, creditLimit: 1000 },
  },
  async run(context, step) {
    // Execute heavy computations concurrently
    const [resultA, resultB] = await step.runParallel([
      async () => computeEmbedding(context.args.query),
      async () => fetchContextData(context.args.filePath),
    ]);

    // Process results through a sequential pipeline
    const final = await step.runPipeline([
      async () => mergeResults(resultA, resultB),
      async (prev) => validateSuggestions(prev),
      async (prev) => formatOutput(prev, context.args.format),
    ]);

    // Must return JSON-serializable data
    return { suggestions: final, timestamp: Date.now() };
  },
});

```

The `run` function can interleave any combination of `step.run`, `step.runParallel`, and `step.runPipeline` calls. Errors thrown within any sub-task bubble up as `FactoryResumeError` instances with specific error codes indicating the failure mode.

## Registration and Session Orchestration

Factories must be registered with a session before invocation. The `session.registerFactories([...])` method in [`nodejs/src/session.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/session.ts) binds factory definitions to the active runtime context.

Once registered, factories become callable through:
- The Copilot UI interface
- Other factories via the step API
- Direct RPC client invocations

The session handles the wire format serialization (`FactoryRunRequest`), result validation, and limit enforcement. When a factory exceeds its `timeoutSeconds` or `creditLimit`, the session terminates execution and raises a `FactoryResumeError` with an appropriate termination code.

## Testing and Validation Patterns

The SDK provides comprehensive test coverage illustrating factory behavior:

- **[`nodejs/test/factory.test.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/test/factory.test.ts)**: Unit tests covering basic execution, parallel fan-out size validation, pipeline chaining, and error serialization scenarios
- **[`nodejs/test/e2e/factory.e2e.test.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/test/e2e/factory.e2e.test.ts)**: End-to-end tests demonstrating real session initialization, factory registration, and cross-process invocation via the RPC client

These test files demonstrate how to mock `FactoryContext` and verify that factories return strictly JSON-serializable data structures.

## Summary

- **Define factories** using `defineFactory()` in [`nodejs/src/factory.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/factory.ts), providing metadata and a `run` function that receives `FactoryContext` and `FactoryStep` APIs.
- **Execute work** through the step API: `step.run()` for sequential tasks, `step.runParallel()` for concurrent fan-out (with automatic fan-out limits), and `step.runPipeline()` for chained data processing.
- **Respect constraints**: Return values must be JSON-serializable (no functions, symbols, or cyclic references), and operations must stay within declared `limits` (timeout, credits).
- **Register and run** factories via `session.registerFactories()` in [`nodejs/src/session.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/session.ts), which orchestrates RPC communication and enforces runtime protections.

## Frequently Asked Questions

### What happens if a factory returns non-JSON-serializable data?

The SDK validates return values before transmission and throws a `FactoryResumeError` if the result contains functions, symbols, `BigInt`, `undefined`, or cyclic structures. This ensures all factory outputs can traverse the JSON-RPC wire format safely.

### How does the Copilot SDK prevent resource exhaustion during parallel execution?

The runtime calls `assertFactoryFanoutSize("parallel", ...)` in [`nodejs/src/session.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/session.ts) before launching concurrent thunks, enforcing a configurable maximum fan-out size. If you exceed this limit, the SDK throws an error before executing the steps.

### Can I mix parallel and pipeline execution within the same factory?

Yes. The `step` API allows unlimited composition of `step.runParallel()` and `step.runPipeline()` calls. You can fan out to gather data in parallel, then feed the aggregated results into a pipeline for sequential processing, as demonstrated in [`nodejs/test/factory.test.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/test/factory.test.ts).

### What is the difference between `step.runPipeline()` and manually chaining `await step.run()` calls?

While both execute sequentially, `step.runPipeline()` provides structured data-flow semantics where each step automatically receives the previous output as its argument. Additionally, the SDK tracks pipeline depth separately and validates it against `assertFactoryFanoutSize("pipeline", ...)` limits, offering better debugging context and resource tracking than manual chaining.