{"_id":"@burner-io/workflow","name":"@burner-io/workflow","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@burner-io/workflow","version":"0.1.0","private":false,"description":"Serializable workflow graph contracts and deterministic runtime with optional Hermes and React Flow adapters.","type":"module","sideEffects":false,"license":"MIT","author":{"name":"Tarek Bachir"},"repository":{"type":"git","url":"git+https://github.com/burner-io/workflow.git"},"bugs":{"url":"https://github.com/burner-io/workflow/issues"},"homepage":"https://github.com/burner-io/workflow#readme","keywords":["workflow","graph","sdk","typescript","hermes","react-flow"],"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./contracts":{"types":"./dist/contracts.d.ts","default":"./dist/contracts.js"},"./hermes":{"types":"./dist/hermes.d.ts","default":"./dist/hermes.js"},"./react-flow":{"types":"./dist/react-flow.d.ts","default":"./dist/react-flow.js"}},"scripts":{"build":"tsc -p tsconfig.json","typecheck":"tsc -p tsconfig.json --noEmit","test":"node --test tests/*.test.mjs","check":"tsc -p tsconfig.json --noEmit && tsc -p tsconfig.json && node --test tests/*.test.mjs","prepublishOnly":"npm run check"},"engines":{"node":">=20"},"devDependencies":{"@burner-io/hermes":"^0.5.0","typescript":"^5.8.3"},"peerDependencies":{"@burner-io/hermes":">=0.5.0 <1"},"peerDependenciesMeta":{"@burner-io/hermes":{"optional":true}},"_id":"@burner-io/workflow@0.1.0","gitHead":"f6c48a88b939851289eeff4242e5925073250ce6","_nodeVersion":"22.23.2","_npmVersion":"10.9.8","dist":{"integrity":"sha512-mjv5gennAqsd/1PhczZYFUItQYVqhSpv0BCQ5jQTE60kfZvL0pIr8kviIxcyezE/F3JsnuVk4I1Gdc6Vfzil5w==","shasum":"6f5d11a4a0b08e810e0783e993cf9c8557d82774","tarball":"https://registry.npmjs.org/@burner-io/workflow/-/workflow-0.1.0.tgz","fileCount":45,"unpackedSize":153938,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQD7T2MNijFgYDyjIcGWmnrW63M9AdL/f/JX5ldbGr2vHgIhAKs83qqgei5QOL3KiAwzVPegF+er8GiVlQ/H7e7iNkL3"}]},"_npmUser":{"name":"strasberry","email":"bachir.tarek.pro@gmail.com"},"directories":{},"maintainers":[{"name":"strasberry","email":"bachir.tarek.pro@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/workflow_0.1.0_1786655745492_0.440045457825454"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-13T21:15:45.347Z","0.1.0":"2026-08-13T21:15:45.643Z","modified":"2026-08-13T21:15:45.832Z"},"maintainers":[{"name":"strasberry","email":"bachir.tarek.pro@gmail.com"}],"description":"Serializable workflow graph contracts and deterministic runtime with optional Hermes and React Flow adapters.","homepage":"https://github.com/burner-io/workflow#readme","keywords":["workflow","graph","sdk","typescript","hermes","react-flow"],"repository":{"type":"git","url":"git+https://github.com/burner-io/workflow.git"},"author":{"name":"Tarek Bachir"},"bugs":{"url":"https://github.com/burner-io/workflow/issues"},"license":"MIT","readme":"# @burner-io/workflow\n\nSerializable workflow contracts and a deterministic TypeScript runtime for composing application-owned workflows.\n\n`@burner-io/workflow` is intentionally **not** part of the Hermes domain. It can delegate a node to Hermes, call an HTTP service, run an application function, branch, wait for a human, or invoke a subworkflow without turning any of those systems into the workflow's ownership model.\n\n## Architecture boundary\n\n```text\n@burner-io/hermes\n  └─ faithful Hermes-native protocols and payloads\n          │\n          │ optional adapter\n          ▼\n@burner-io/workflow\n  ├─ serializable graph\n  ├─ deterministic execution\n  ├─ bindings / routing / retries\n  ├─ human wait + resume\n  └─ app-owned composition\n          │\n          │ optional adapter\n          ▼\nReact Flow + AI Elements\n  └─ editor / visualization owned by the application\n```\n\nThe important direction is one-way: **the workflow knows how to delegate to Hermes; Hermes does not know that a workflow exists.**\n\n## Install\n\nCore only:\n\n```bash\nnpm install @burner-io/workflow\n```\n\nHermes node support:\n\n```bash\nnpm install @burner-io/workflow @burner-io/hermes\n```\n\nEditor example:\n\n```bash\nnpm install @xyflow/react\nnpx ai-elements@latest add canvas connection edge node panel toolbar\nnpx shadcn@latest add button badge\n```\n\nLoad React Flow's stylesheet once in the consuming app (for example from the app's global stylesheet/layout):\n\n```ts\nimport \"@xyflow/react/dist/style.css\";\n```\n\nThe package itself has no React or React Flow runtime dependency. `@burner-io/hermes` is an optional peer used only by the `@burner-io/workflow/hermes` subpath.\n\n## Define a workflow\n\nA node can be an entry itself. This lets the common case start directly with a Hermes discussion/run rather than requiring an artificial target node:\n\n```ts\nimport type { WorkflowDefinition } from \"@burner-io/workflow\";\n\nexport const workflow: WorkflowDefinition = {\n  id: \"request-flow\",\n  version: \"1.0.0\",\n  entryNodeIds: [\"discussion\"],\n  nodes: [\n    {\n      id: \"discussion\",\n      type: \"hermes.run\",\n      name: \"Discussion\",\n      config: {\n        instructions: \"Understand the request and produce a precise handoff.\",\n        streamEvents: true,\n      },\n    },\n    {\n      id: \"api\",\n      type: \"http.request\",\n      name: \"External API\",\n      config: {\n        method: \"POST\",\n        url: \"/api/enrich\",\n        body: {\n          request: { $from: \"nodes.discussion.output.output\" },\n        },\n      },\n    },\n    {\n      id: \"out\",\n      type: \"output\",\n      name: \"Complete\",\n      config: {},\n    },\n  ],\n  edges: [\n    { id: \"e1\", source: \"discussion\", target: \"api\" },\n    { id: \"e2\", source: \"api\", target: \"out\" },\n  ],\n};\n```\n\nThe full example in `examples/next/sample-workflow.ts` demonstrates one Hermes entry node fanning out to an API and another Hermes connection before joining at a condition and human approval gate.\n\n## Data bindings\n\nWorkflow config stays JSON-serializable. Dynamic values are expressed with `$from` rather than JavaScript closures:\n\n```ts\n{\n  request: { $from: \"input.request\", $required: true },\n  previous: { $from: \"nodes.analyze.output\" },\n  error: { $from: \"nodes.call-api.error.message\", $default: \"unknown\" },\n  joined: { $from: \"incoming\" },\n  runId: { $from: \"run.id\" },\n}\n```\n\nAvailable roots:\n\n- `input` — original workflow input;\n- `nodes.<id>` — runtime state including `input`, `output`, `error`, status and attempts;\n- `incoming` — active incoming edge payloads for the current node;\n- `run` — stable run metadata.\n\nBindings can appear recursively inside node inputs, HTTP bodies/headers/URLs, transforms, predicates, Hermes run options, edge maps and workflow outputs.\n\n## Execute\n\n```ts\nimport {\n  WorkflowEngine,\n  InMemoryWorkflowRunStore,\n  createBuiltinWorkflowExecutors,\n} from \"@burner-io/workflow\";\n\nconst store = new InMemoryWorkflowRunStore();\n\nconst engine = new WorkflowEngine({\n  store,\n  concurrency: 4,\n  executors: createBuiltinWorkflowExecutors({\n    functions: {\n      normalize: (input) => ({ normalized: input }),\n    },\n  }),\n  onEvent(event) {\n    console.log(event.type, event.nodeId, event.data);\n  },\n});\n\nconst run = await engine.start(workflow, {\n  request: \"Prepare the release\",\n});\n```\n\nThe engine executes ready nodes in deterministic waves up to `concurrency`, supports fan-out/fan-in, retries, timeouts, conditional handles, error edges and explicit terminal output projection.\n\n## Hermes Runs\n\nThe Hermes adapter delegates a workflow node to the **native Hermes API Server Runs protocol**. It does not emulate an agent and it returns the full native Hermes run object unchanged as the node output.\n\n```ts\nimport { createApiServerApi } from \"@burner-io/hermes\";\nimport { WorkflowEngine, createBuiltinWorkflowExecutors } from \"@burner-io/workflow\";\nimport { createHermesWorkflowExecutors } from \"@burner-io/workflow/hermes\";\n\nconst main = createApiServerApi({\n  baseUrl: process.env.HERMES_API_URL!,\n  apiKey: process.env.HERMES_API_KEY,\n  profile: \"default\",\n});\n\nconst specialist = createApiServerApi({\n  baseUrl: process.env.HERMES_API_URL!,\n  apiKey: process.env.HERMES_API_KEY,\n  profile: \"specialist\",\n});\n\nconst engine = new WorkflowEngine({\n  executors: {\n    ...createBuiltinWorkflowExecutors(),\n    ...createHermesWorkflowExecutors({\n      defaultApiServer: main,\n      apiServers: { specialist },\n    }),\n  },\n});\n```\n\nA `hermes.run` node may select a registered connection and project templates into the native run request:\n\n```ts\n{\n  id: \"review\",\n  type: \"hermes.run\",\n  input: { $from: \"nodes.prepare.output\" },\n  config: {\n    connection: \"specialist\",\n    instructions: \"Review this implementation.\",\n    model: { $from: \"input.model\" },\n    streamEvents: true,\n  },\n}\n```\n\nNative run SSE events are forwarded into the workflow event log as `hermes.run.event`. Cancelling/timing out the workflow node also requests the native Hermes run's stop endpoint on a best-effort basis.\n\nThe workflow adapter does **not** reinterpret Hermes model output, tools, decisions or reasoning. Hermes retains autonomy for that whole node.\n\n## Human gates and durable resume\n\nA human node returns a serializable wait state instead of blocking a process:\n\n```ts\n{\n  id: \"approve\",\n  type: \"human\",\n  config: {\n    mode: \"approval\",\n    prompt: \"Ship this release?\",\n  },\n}\n```\n\n`start()` persists and returns a run with `status: \"waiting\"`. The UI/API can resume it later:\n\n```ts\nawait engine.resume(workflow, run.id, {\n  nodeId: \"approve\",\n  value: { approved: true, by: \"user-123\" },\n});\n```\n\n`InMemoryWorkflowRunStore` is for tests/prototypes. Production applications should implement the tiny `WorkflowRunStore` interface with their database/queue of choice.\n\n## Conditions and routers\n\n`condition` emits one source handle: `true` or `false`.\n\n```ts\n{\n  id: \"gate\",\n  type: \"condition\",\n  config: {\n    predicate: {\n      op: \"gte\",\n      left: { $from: \"nodes.score.output.value\" },\n      right: 0.8,\n    },\n  },\n}\n```\n\nEdges target the route explicitly:\n\n```ts\n{ id: \"yes\", source: \"gate\", sourceHandle: \"true\", target: \"publish\" }\n{ id: \"no\", source: \"gate\", sourceHandle: \"false\", target: \"review\" }\n```\n\n`router` generalizes this to named handles and either first-match or all-match routing.\n\n## Error paths\n\nErrors do not become success values implicitly. A node remains `failed`, while an explicit `on: \"error\"` edge can recover the graph:\n\n```ts\n{\n  id: \"recover\",\n  source: \"external-api\",\n  target: \"fallback\",\n  on: \"error\",\n  map: {\n    message: { $from: \"nodes.external-api.error.message\" },\n  },\n}\n```\n\nThe completed run therefore retains the historical failure while also exposing the recovery result.\n\n## React Flow / AI Elements\n\n`@burner-io/workflow/react-flow` exports structural adapters only:\n\n```ts\nimport { workflowToReactFlow } from \"@burner-io/workflow/react-flow\";\n\nconst { nodes, edges } = workflowToReactFlow(workflow, {\n  layout,\n  run,\n});\n```\n\nRuntime-active edges map to the AI Elements `animated` type; other graph paths use `temporary`. Source/target handles and node runtime state are preserved.\n\nSee `examples/next/WorkflowBuilder.tsx` for a copyable editor using AI Elements' `Canvas`, `Node`, `Edge`, `Connection`, `Panel`, `Toolbar`, and React Flow controlled state. The editor is an example rather than compiled package UI because AI Elements components are installed as source into each shadcn application.\n\n## Node catalog\n\nBuilt-ins in V0.1:\n\n- `input`\n- `output`\n- `function`\n- `http.request`\n- `transform`\n- `condition`\n- `router`\n- `human`\n- `subworkflow`\n- `hermes.run` through the optional Hermes adapter\n\nCustom node types are supported by adding a serializable node contract in the application and registering an executor under its `type` key. The core runtime has no global plugin registry or service locator.\n\n## Deliberate V0.1 constraints\n\n- Graph cycles are rejected. Looping must become an explicit future loop node with bounds/state rather than an accidental React Flow cycle.\n- The bundled run store is in-memory only.\n- Native Hermes approval events are forwarded, but automatic conversion of a Hermes approval request into a workflow `human` wait/resume/approve handshake is not bundled yet.\n- `subworkflow` delegates to an application callback; the package does not invent a workflow catalog/database model.\n- The HTTP executor is deliberately generic and has no secret vault or auth policy layer.\n\nSee `docs/EXECUTION-SEMANTICS.md`, `docs/NODE-CATALOG.md`, `docs/HERMES-BOUNDARY.md`, and `VALIDATION.md`.\n","readmeFilename":"README.md","_rev":"1-243f2354a3e29753299f6a7a93dd8ba8"}