Parallel Workers — Fan-Out / Fan-In (TypeScript)
SkillAI & modelsRuns several sub-agents at once inside a TypeScript Golem app and gathers their results.
Use Parallel Workers — Fan-Out / Fan-In (TypeScript) in Claude, ChatGPT or Ahel Desktop
Free. Sign in, add Parallel Workers — Fan-Out / Fan-In (TypeScript) and connect your AI. About a minute.
Also: Claude Code · Cursor · Codex
Then ask your AI: use the Parallel Workers skill
Details
Instructions available. Your AI can read the instructions. Execution depends on the setup they require.
Account requirements not reviewed. Check the skill instructions before use; Ahel provides instructions and does not run this skill.
No other account needed.
Add Ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.
About this skill
Fan out work to multiple parallel agents and collect results in a TypeScript Golem project. Use when the user asks about parallel execution, fan-out/fan-in, spawning child agents for parallel work, forking, or aggregating results from multiple agents.
What this skill tells your AI
The instructions your AI receives, as published by golemcloud/golem in golem-skills/skills/ts/golem-parallel-workers-ts/SKILL.md and read by Ahel’s review.
Overview
Golem agents process invocations sequentially — a single agent cannot run work in parallel. To execute work concurrently, distribute it across multiple agent instances. This skill covers two approaches:
- Child agents via
AgentDef.client.get(id)— spawn separate agent instances, dispatch work, and collect results fork()— clone the current agent at the current execution point for lightweight parallel execution
Approach 1: Child Agent Fan-Out
Spawn child agents, call them concurrently with Promise.all, and aggregate results.
Basic Pattern
import { z } from 'zod';
import { defineAgent, method } from '@golemcloud/golem-ts-sdk';
export const Worker = defineAgent({
name: 'Worker',
id: { id: z.number() },
methods: {
process: method({ input: { data: z.string() }, returns: z.string() }),
},
});
export const WorkerImpl = Worker.implement({
init: ({ id }) => ({ id: id.id }),
methods: {
process({ data }) {
return `processed-${data}`;
},
},
});
export const Coordinator = defineAgent({
name: 'Coordinator',
id: { name: z.string() },
methods: {
fanOut: method({ input: { items: z.array(z.string()) }, returns: z.array(z.string()) }),
},
});
export const CoordinatorImpl = Coordinator.implement({
init: () => ({}),
methods: {
async fanOut({ items }) {
// Spawn one child per item and call concurrently.
const promises = items.map((item, i) => Worker.client.get({ id: i }).process({ data: item }));
// Wait for all children to finish.
return await Promise.all(promises);
},
},
});
Chunked Fan-Out
When spawning many children, batch them to limit concurrency:
async fanOutChunked({ ids }) {
const chunks = arrayChunks(ids, 5); // Process 5 at a time
const results: number[] = [];
for (const chunk of chunks) {
const promises = chunk.map((id) => Worker.client.get({ id }).compute({ n: id }));
results.push(...await Promise.all(promises));
}
return results;
}
function arrayChunks<T>(arr: T[], size: number): T[][] {
const chunks: T[][] = [];
for (let i = 0; i < arr.length; i += size) {
chunks.push(arr.slice(i, i + size));
}
return chunks;
}
Fire-and-Forget with Promise Collection
For long-running work, trigger children with .trigger() and collect results via
Golem promises. A PromiseId is a nested record of bigints, so ship it across the
agent boundary as a bigint-aware JSON string:
import { z } from 'zod';
import {
defineAgent, method,
createPromise, awaitPromise, completePromise, PromiseId,
} from '@golemcloud/golem-ts-sdk';
export function encodePromiseId(id: PromiseId): string {
return JSON.stringify(id, (_k, v) => (typeof v === 'bigint' ? { '#bigint': v.toString() } : v));
}
export function decodePromiseId(text: string): PromiseId {
return JSON.parse(text, (_k, v) =>
v && typeof v === 'object' && '#bigint' in v ? BigInt(v['#bigint']) : v,
) as PromiseId;
}
export const RegionWorker = defineAgent({
name: 'RegionWorker',
id: { region: z.string() },
methods: {
runReport: method({ input: { promiseId: z.string() }, returns: z.void() }),
},
});
export const RegionWorkerImpl = RegionWorker.implement({
init: ({ id }) => ({ region: id.region }),
methods: {
runReport({ promiseId }) {
const report = `Report for ${this.region}: OK`;
completePromise(decodePromiseId(promiseId), new TextEncoder().encode(report));
},
},
});
// Inside a coordinator method handler:
async dispatchAndCollect({ regions }) {
// Create one promise per child.
const promiseIds = regions.map(() => createPromise());
// Fire-and-forget: trigger each child with its (encoded) promise ID.
regions.forEach((region, i) => {
RegionWorker.client.get({ region }).runReport.trigger({ promiseId: encodePromiseId(promiseIds[i]) });
});
// Collect all results (the agent suspends until each promise completes).
return await Promise.all(
promiseIds.map(async (pid) => new TextDecoder().decode(await awaitPromise(pid))),
);
}
Error Handling
Use Promise.allSettled to handle partial failures:
async fanOutWithErrors({ items }) {
const promises = items.map((item, i) => Worker.client.get({ id: i }).process({ data: item }));
const settled = await Promise.allSettled(promises);
const successes: string[] = [];
const failures: string[] = [];
settled.forEach((result, i) => {
if (result.status === 'fulfilled') {
successes.push(result.value);
} else {
failures.push(`Item ${items[i]} failed: ${result.reason}`);
}
});
return { successes, failures };
}
Approach 2: fork()
fork() clones the current agent at the current execution point, creating a new agent instance with the same state but a unique phantom ID. Use Golem promises to synchronize between the original and forked agents.
Basic Fork Pattern
import { z } from 'zod';
import {
defineAgent, method,
fork, createPromise, awaitPromise, completePromise,
} from '@golemcloud/golem-ts-sdk';
export const ForkAgent = defineAgent({
name: 'ForkAgent',
id: { name: z.string() },
methods: {
parallelCompute: method({ input: {}, returns: z.string() }),
},
});
export const ForkAgentImpl = ForkAgent.implement({
init: () => ({}),
methods: {
async parallelCompute() {
const promiseId = createPromise();
const result = fork();
switch (result.tag) {
case 'original': {
// Wait for the forked agent to complete the promise.
const bytes = await awaitPromise(promiseId);
const forkedResult = new TextDecoder().decode(bytes);
return `Combined: original + ${forkedResult}`;
}
case 'forked': {
// Do work in the forked copy.
const computed = 'forked-result';
completePromise(promiseId, new TextEncoder().encode(computed));
return 'forked done'; // This return is only seen by the forked agent.
}
}
},
},
});
Multi-Fork Fan-Out
Fork multiple times for N-way parallelism:
async multiFork({ n }) {
const promiseIds = Array.from({ length: n }, () => createPromise());
for (let i = 0; i < n; i++) {
const result = fork();
if (result.tag === 'forked') {
// Each forked agent does its slice of work.
const output = `result-from-fork-${i}`;
completePromise(promiseIds[i], new TextEncoder().encode(output));
return []; // Forked agent exits here.
}
}
// Original agent collects all results.
return await Promise.all(
promiseIds.map(async (pid) => new TextDecoder().decode(await awaitPromise(pid))),
);
}
When to Use Which Approach
| Criteria | Child Agents | fork() |
|---|---|---|
| Work is independent and stateless | ✅ Best fit | Works but overkill |
| Need to share current state with workers | ❌ Must pass via args | ✅ Forked copy inherits state |
| Workers need persistent identity | ✅ Each has own ID | ❌ Forked agents are ephemeral phantoms |
| Number of parallel tasks is dynamic | ✅ Spawn as many as needed | ✅ Fork in a loop |
| Need simple error isolation | ✅ Child failure doesn't crash parent | ⚠️ Forked agent shares oplog lineage |
Key Points
- No threads: Golem is single-threaded per agent — parallelism is achieved by distributing across agent instances
- Durability: All RPC calls, promises, and fork operations are durably recorded — work survives crashes
- Deadlock avoidance: Never have two agents awaiting each other synchronously — use
.trigger()to break cycles - Cleanup: Child agents persist after the coordinator finishes; delete them explicitly if they hold unwanted state
Signals
- GitHub stars
- 2k
- Forks
- 210
- Last commit
- Oct 2026
Advanced
- Item type
- skill
- Key
golem-parallel-workers-ts- Source
- github.com/golemcloud/golem
Related picks
Skill · mattpocock
The pick for TypeScripttypescript-pro
Skill · jeffallan
The pick for TypeScriptskill-creator
Skill · anthropics
More in AI & modelstriage
Skill · mattpocock
More in AI & modelsalgorithmic-art
Skill · anthropics
More in AI & modelscode-review-and-quality
Skill · addyosmani
More in AI & models