ISOLATION CAVEAT (READ THIS FIRST)
This example uses
InprocWorkerExecutor— a TEST FIXTURE frompackages/pangolin-orchestrator/test/fixtures/inproc-worker-executor.ts.InprocWorkerExecutor runs worker pipelines IN-PROCESS. There is no container sandbox, no network firewall, no filesystem isolation. Scripts run directly in the Node.js process that drives the orchestrator.
This executor is a demo/test bridge only. It MUST NEVER be used in production. Production dispatches use
DispatchExecutor, which submits work to an isolated compute layer (Fargate, local-docker, etc.).
Second-domain forcing function: the map-reduce pattern works for data pipelines with zero engine changes. The entire orchestration engine (pattern, needs-resolver, audit chain, verifyBundle) is reused unchanged from the dev-domain dogfood example.
Reference spec: docs/superpowers/specs/2026-06-05-pangolin-block-runner-data-pack-design.md
(the block-pipeline runner + data pack design this example demonstrates).
seed ──[outputs/data.csv]──▶ split ──[mapReduce config]
│
┌─────────────────┤
▼ ▼
map-a.csv map-b.csv
│ │
└────────┬────────┘
▼
reduce
Two items are submitted; three more are spawned at runtime by the
mapReduce pattern:
| Item | Role | Pipeline registered |
|---|---|---|
seed |
submitted | data-mapreduce.seed |
split |
submitted | data-mapreduce.split |
map-a.csv |
spawned | data-mapreduce.transform |
map-b.csv |
spawned | data-mapreduce.transform |
reduce |
spawned | data-mapreduce.aggregate |
-
seed writes a small CSV to
outputs/data.csv:group,value a,10 a,20 b,30 b,40 -
split receives the CSV at
inputs/input(vianeeds: { input: { from: seed, ... } }), groups rows by the first column, writesoutputs/a.csvandoutputs/b.csv. ThemapReduceconfig on this item causes the engine to spawn onemap-<key>item per output file. -
map-a.csv / map-b.csv each receive one group's CSV at
inputs/input, sum the value column, and write the sum tooutputs/result. -
reduce receives all map results at
inputs/part-a.csvandinputs/part-b.csv(via the pattern'skeyPrefixnaming), totals them, and writes the grand total tooutputs/result.
Expected result: 100 (a=30, b=70).
plan.json contains the structural skeleton of the run. Pipeline and
subagent refs are content hashes that are only known after runtime
registration. Those slots are marked "<filled-at-runtime>":
{ "pipeline": "<filled-at-runtime>" }src/index.ts registers all four pipelines + one stub subagent via
registerSubagent / registerPipeline (real client APIs, shared
LocalStorageProvider), then fills the placeholders with the returned
pinned URIs before calling orch.submitRun(plan).
Every dispatch produces a manifest blob (stored at the dispatch record URI
in the shared LocalStorageProvider). assembleBundle + verifyBundle
walk the audit chain and verify that every input ref is accounted for in
a sealed manifest — the same provenance check used in production.
pnpm install # from repo root
pnpm --filter data-mapreduce-example start # exits 0 on success
pnpm --filter data-mapreduce-example typecheck # TypeScript static checkNo Docker, no API key, no network. All storage is in a temporary directory that is deleted on exit.
=== Grown graph (2 submitted → seed+split+maps+reduce) ===
map-a.csv: done
map-b.csv: done
reduce: done
seed: done
split: done
Total items: 5
=== Aggregate numeric result (reduce outputRef) ===
reduce result: 100
=== blocks[] evidence sample (sentinel of map-a.csv) ===
sentinel.blocks (2 entries):
[0] kind=script status=ok
[1] kind=capture status=ok
=== verifyBundle report ===
intact: true
checks.handoff: {"ok":true,"detail":"5 input refs accounted for"}
=== data-mapreduce OK — graph grew at runtime (5 items); aggregate sum=100; provenance intact ===