to ensure compatibility with the new DSL, we are removing the ESM import and callback approach for parsing JS bodies. These are essentially "fake" JS files without any package import capabilities, and certain syntax issues can lead to execution problems
Workflow Runtime: Old vs New Comparison
Old: ES Module Import
// loader.ts
const mod = await import(file);
return { meta: mod.meta, run: mod.default };
// workflow file
import { readFile } from "fs/promises";
export const meta = {
name: "review",
description: "Review files",
};
export default async function ({ agent, pipeline, parallel, phase, log, args }) {
phase("Discover");
const files = await agent("Find .ts files", { schema: { type: "array", items: { type: "string" } } });
return { files };
}
New: Text Read + AsyncFunction with Globals
// loader.ts
const source = await readFile(file, "utf-8");
const meta = extractMeta(source); // regex + new Function('return ({...})')()
const body = extractBody(source); // strips export const meta block
// executor.ts
const fn = new AsyncFunction("agent", "pipeline", "parallel", "phase", "log", "args", body);
return fn(runtime.agent, runtime.pipeline, runtime.parallel, runtime.phase, runtime.log, args);
// workflow file
export const meta = {
name: "review",
description: "Review files",
};
phase("Discover");
const files = await agent("Find .ts files", { schema: { type: "array", items: { type: "string" } } });
return { files };
Comparison
|
Old (import()) |
New (AsyncFunction) |
| Workflow file format |
Requires export default async function wrapper |
Flat script — no wrapper needed |
Static import statements |
Work natively |
Broken — must use await import() instead |
export const meta |
Real ES export, parsed by engine |
Cosmetic — extracted via regex + eval |
| Source maps / stack traces |
Accurate line numbers |
Offset (meta block stripped) |
| Syntax error reporting |
V8/JSC-level, points to exact file + line |
Generic "error in anonymous function" |
| Module caching |
Bun/Node caches the module |
Fresh eval every execution |
| Relative imports |
Resolve correctly from workflow dir |
Not applicable (no static imports) |
| File discovery/reads |
One import() call does everything |
readFile + regex parse + new Function |
| Concurrent execution safety |
Fully isolated (each import is a module) |
Fully isolated (args passed to function) |
| Boilerplate for workflow authors |
~2 lines (export default async function ({...}) { + }) |
Zero |
| Compatible with Claude-style workflows |
No — Claude outputs flat scripts with implicit globals |
Yes — direct match to the DSL |
return at top level |
Inside function body (valid) |
Becomes return of AsyncFunction (valid) |
Dynamic await import() |
Works |
Works |
| TypeScript workflow files |
Handled by Bun's loader |
Would need separate transpile step |
Key Tradeoff
Old = robust module system, full JS/TS features, but requires the export default function ceremony.
New = zero-boilerplate flat scripts that match what Claude naturally generates, but sacrifices static imports and proper error reporting.
The target DSL (53 real-world workflows) uses zero static imports — all I/O is delegated to agent(). This makes the "no static imports" downside irrelevant for the intended use case.
to ensure compatibility with the new DSL, we are removing the ESM import and callback approach for parsing JS bodies. These are essentially "fake" JS files without any package import capabilities, and certain syntax issues can lead to execution problems
Workflow Runtime: Old vs New Comparison
Old: ES Module Import
New: Text Read + AsyncFunction with Globals
Comparison
import())AsyncFunction)export default async functionwrapperimportstatementsawait import()insteadexport const metaimport()call does everythingreadFile+ regex parse +new Functionexport default async function ({...}) {+})returnat top levelawait import()Key Tradeoff
Old = robust module system, full JS/TS features, but requires the
export default functionceremony.New = zero-boilerplate flat scripts that match what Claude naturally generates, but sacrifices static imports and proper error reporting.
The target DSL (53 real-world workflows) uses zero static imports — all I/O is delegated to
agent(). This makes the "no static imports" downside irrelevant for the intended use case.