Control Flow

fork

Run multiple steps in parallel with race, all, or settle semantics.

Quick Example

import { fork, step } from '@noetic/core';

const fastest = fork({
  id: 'fastest-response',
  mode: 'race',
  paths: (input: string) => [
    step.llm({
      id: 'model-a',
      model: 'gpt-4o',
    }),
    step.llm({
      id: 'model-b',
      model: 'gpt-4o-mini',
    }),
  ],
});

What It Does

fork runs multiple steps concurrently and combines the results. There are three modes that determine how results are collected.

Modes

race

Returns the output of the first step to complete. All other paths are discarded.

import { fork, step } from '@noetic/core';

const raceExample = fork({
  id: 'race-providers',
  mode: 'race',
  paths: (query: string) => [
    step.run({
      id: 'provider-a',
      execute: async (q) => fetchProviderA(q),
    }),
    step.run({
      id: 'provider-b',
      execute: async (q) => fetchProviderB(q),
    }),
  ],
});

No merge function is needed -- the winner's output is used directly.

all

Waits for every path to complete, then calls merge with an array of all results.

import { fork, step } from '@noetic/core';

const allExample = fork({
  id: 'gather-research',
  mode: 'all',
  paths: (topic: string) => [
    step.run({
      id: 'academic',
      execute: async (t) => searchAcademic(t),
    }),
    step.run({
      id: 'news',
      execute: async (t) => searchNews(t),
    }),
    step.run({
      id: 'social',
      execute: async (t) => searchSocial(t),
    }),
  ],
  merge: (results) => results.flat(),
});

If any path throws, the entire fork throws.

settle

Like all, but collects both successes and failures. Each result is a SettleResult with a status field.

import type { SettleResult } from '@noetic/core';

import { fork, step } from '@noetic/core';

const settleExample = fork({
  id: 'resilient-search',
  mode: 'settle',
  paths: (query: string) => [
    step.run({
      id: 'source-a',
      execute: async (q) => fetchSourceA(q),
    }),
    step.run({
      id: 'source-b',
      execute: async (q) => fetchSourceB(q),
    }),
  ],
  merge: (results: SettleResult<string>[]) => {
    const successes = results
      .filter((r) => r.status === 'fulfilled')
      .map((r) => r.value);
    return successes.join('\n');
  },
});

SettleResult Type

PropertyTypeDescription
stepIdstringThe id of the step that produced this result
status'fulfilled' | 'rejected'Whether the step succeeded or failed
valueO | undefinedThe output value (present when fulfilled)
errorNoeticError | undefinedThe error (present when rejected)

API Reference

Common Options (all modes)

PropertyTypeRequiredDescription
idstringYesUnique step identifier
mode'race' | 'all' | 'settle'YesExecution mode
paths(input: I, ctx: Context) => Step<I, O>[]YesFunction returning the steps to run
concurrencynumberNoMax parallel executions

Mode-Specific Options

PropertyModesTypeDescription
mergeall(results: O[], ctx: Context) => OCombine all outputs into one
mergesettle(results: SettleResult<O>[], ctx: Context) => OCombine settled results into one

Concurrency Control

Limit how many paths run at once with concurrency.

import { fork, step } from '@noetic/core';

const throttled = fork({
  id: 'throttled-fork',
  mode: 'all',
  paths: (input: string) => generateManySteps(input),
  merge: (results) => results,
  concurrency: 3, // at most 3 paths execute simultaneously
});
  • branch -- conditional routing for single-path decisions.
  • Spawn -- isolated child contexts for deeper isolation.
  • Steps -- the step types you can use as fork paths.

On this page