{"id":44853957,"url":"https://github.com/run-llama/workflows-ts","last_synced_at":"2026-02-17T07:01:35.109Z","repository":{"id":282932714,"uuid":"948834607","full_name":"run-llama/workflows-ts","owner":"run-llama","description":"🌊 Simple, event-driven and stream oriented workflow for TypeScript","archived":false,"fork":false,"pushed_at":"2025-12-02T07:13:06.000Z","size":4736,"stargazers_count":254,"open_issues_count":9,"forks_count":18,"subscribers_count":4,"default_branch":"main","last_synced_at":"2026-01-22T00:52:31.029Z","etag":null,"topics":["agentic-ai","agentic-framework","agents","flow","reactive-programming","streaming","typescript","workflow"],"latest_commit_sha":null,"homepage":"https://ts.llamaindex.ai/docs/llamaflow","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/run-llama.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2025-03-15T04:05:06.000Z","updated_at":"2026-01-18T17:11:14.000Z","dependencies_parsed_at":"2025-03-17T18:40:48.468Z","dependency_job_id":"42775b3a-07df-48e1-a8c7-eb80e42e45af","html_url":"https://github.com/run-llama/workflows-ts","commit_stats":null,"previous_names":["run-llama/fluere","run-llama/llama-flow","run-llama/workflows-ts"],"tags_count":82,"template":false,"template_full_name":null,"purl":"pkg:github/run-llama/workflows-ts","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/run-llama%2Fworkflows-ts","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/run-llama%2Fworkflows-ts/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/run-llama%2Fworkflows-ts/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/run-llama%2Fworkflows-ts/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/run-llama","download_url":"https://codeload.github.com/run-llama/workflows-ts/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/run-llama%2Fworkflows-ts/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29536527,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-17T05:00:25.817Z","status":"ssl_error","status_checked_at":"2026-02-17T04:57:16.126Z","response_time":100,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agentic-ai","agentic-framework","agents","flow","reactive-programming","streaming","typescript","workflow"],"created_at":"2026-02-17T07:01:02.391Z","updated_at":"2026-02-17T07:01:35.104Z","avatar_url":"https://github.com/run-llama.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"# LlamaIndex Workflows TS\n\n🌊 is a simple, lightweight workflow engine, in TypeScript.\n\n[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz_small.svg)](https://stackblitz.com/github/run-llama/workflows-ts/tree/main/demo/browser?file=src%2FApp.tsx)\n[![Build Status](https://img.shields.io/github/actions/workflow/status/run-llama/workflows-ts/test.yml?branch=main\u0026style=flat\u0026colorA=000000\u0026colorB=45dff8)](https://github.com/run-llama/workflows-ts/actions/workflows/test.yml?query=branch%3Amain)\n[![Bundle Size](https://img.shields.io/bundlephobia/minzip/@llamaindex/workflow-core?style=flat\u0026colorA=000000\u0026colorB=45dff8)](https://bundlephobia.com/result?p=@llamaindex/workflow-core)\n\n- Minimal core API (\u003c=2kb)\n- 100% Type safe\n- Event-driven, stream oriented programming\n- Support multiple JS runtime/framework\n\n## Usage\n\n```shell\nnpm i @llamaindex/workflow-core\n\nyarn add @llamaindex/workflow-core\n\npnpm add @llamaindex/workflow-core\n\nbun add @llamaindex/workflow-core\n\ndeno add npm:@llamaindex/workflow-core\n```\n\n### Demos\n\nFor examples, check out the [demo folder](./demo).\n\n### First, define events\n\n```ts\nimport { workflowEvent } from \"@llamaindex/workflow-core\";\n\nconst startEvent = workflowEvent\u003cstring\u003e();\nconst stopEvent = workflowEvent\u003c1 | -1\u003e();\n```\n\n### Connect events with workflow\n\n```ts\nimport { createWorkflow } from \"@llamaindex/workflow-core\";\n\nconst convertEvent = workflowEvent();\n\nconst workflow = createWorkflow();\n\nworkflow.handle([startEvent], (start) =\u003e {\n  return convertEvent.with(Number.parseInt(start.data, 10));\n});\nworkflow.handle([convertEvent], (convert) =\u003e {\n  return stopEvent.with(convert.data \u003e 0 ? 1 : -1);\n});\n```\n\n### Trigger workflow\n\n```ts\nimport { pipeline } from \"node:stream/promises\";\n\nconst { stream, sendEvent } = workflow.createContext();\nsendEvent(startEvent.with());\nconst result = await pipeline(stream, async function (source) {\n  for await (const event of source) {\n    if (stopEvent.include(event)) {\n      return \"stop received!\";\n    }\n  }\n});\nconsole.log(result); // stop received!\n// or\nconst allEvents = await stream.until(stopEvent).toArray();\n```\n\n### Helper Functions for Common Tasks\n\nThere are helper functions to make working with workflows even simpler:\n\n```ts\nimport {\n  runWorkflow,\n  runAndCollect,\n  runWorkflowWithFilter,\n} from \"@llamaindex/workflow-core/stream/run\";\n\n// Run workflow and get final result\nconst result = await runWorkflow(workflow, startEvent.with(\"42\"), stopEvent);\n\n// Run workflow and collect all events\nconst allEvents = await runAndCollect(\n  workflow,\n  startEvent.with(\"42\"),\n  stopEvent,\n);\n```\n\n### Fan-out (Parallelism)\n\nBy default, we provide a simple fan-out utility to run multiple workflows in parallel\n\n- `context.sendEvent` will emit a new event to current workflow\n- `context.stream` will return a stream of events emitted by the sub-workflow\n\n```ts\nlet condition = false;\nworkflow.handle([startEvent], async (context, start) =\u003e {\n  const { sendEvent, stream } = context;\n  for (let i = 0; i \u003c 10; i++) {\n    sendEvent(convertEvent.with(i));\n  }\n  // You define the condition to stop the workflow\n  const results = await stream\n    .until(() =\u003e condition)\n    .filter(convertStopEvent)\n    .toArray();\n  console.log(results.length); // 10\n  return stopEvent.with();\n});\n\nworkflow.handle([convertEvent], (convert) =\u003e {\n  if (convert.data === 9) {\n    condition = true;\n  }\n  return convertStopEvent.with(/* ... */);\n});\n```\n\n### With RxJS, or any stream API\n\nWorkflow is event-driven, you can use any stream API to handle the workflow like `rxjs`\n\n```ts\nimport { from, pipe } from \"rxjs\";\n\nconst { stream, sendEvent } = workflow.createContext();\n\nfrom(stream)\n  .pipe(filter((ev) =\u003e eventSource(ev) === messageEvent))\n  .subscribe((ev) =\u003e {\n    console.log(ev.data);\n  });\n\nsendEvent(fileParseWorkflow.startEvent(directory));\n```\n\n### Connect with Server endpoint\n\nWorkflow can be used as middleware in any server framework, like `express`, `hono`, `fastify`, etc.\n\n```ts\nimport { Hono } from \"hono\";\nimport { serve } from \"@hono/node-server\";\nimport { createHonoHandler } from \"@llamaindex/workflow-core/interrupter/hono\";\nimport {\n  agentWorkflow,\n  startEvent,\n  stopEvent,\n} from \"../workflows/tool-call-agent.js\";\n\nconst app = new Hono();\n\napp.post(\n  \"/workflow\",\n  createHonoHandler(\n    agentWorkflow,\n    async (ctx) =\u003e startEvent(await ctx.req.text()),\n    stopEvent,\n  ),\n);\n\nserve(app, ({ port }) =\u003e {\n  console.log(`Server started at http://localhost:${port}`);\n});\n```\n\n### Error Handling\n\nYou can use `signal` in the context parameter to handle error\n\n```ts\nworkflow.handle([convertEvent], (context) =\u003e {\n  const { signal } = context;\n\n  signal.onabort = () =\u003e {\n    console.error(\"error in convert event:\", abort.reason);\n  };\n});\n```\n\n### Context Parameter\n\nWorkflow handlers receive the context as the first parameter, providing access to `sendEvent`, `stream`, and `signal`.\n\n```ts\nworkflow.handle([startEvent], async (context) =\u003e {\n  const { sendEvent, stream, signal } = context;\n  // Use context properties directly\n  sendEvent(processEvent.with());\n});\n```\n\n## Middleware\n\n### `withState`\n\nAdding a `state` property to the workflow context, which returns a state object, each state is linked to the workflow\ncontext.\n\n```ts\nimport { createStatefulMiddleware } from \"@llamaindex/workflow-core/middleware/state\";\n\nconst { withState } = createStatefulMiddleware(() =\u003e ({\n  pendingTasks: new Set\u003cPromise\u003cunknown\u003e\u003e(),\n}));\n\nconst workflow = withState(createWorkflow());\n\nworkflow.handle([startEvent], (context) =\u003e {\n  const { state } = context;\n  state.pendingTasks.add(\n    new Promise((resolve) =\u003e {\n      setTimeout(() =\u003e {\n        resolve();\n      }, 100);\n    }),\n  );\n});\n\nconst { state } = workflow.createContext();\n```\n\nYou can also create a state with input:\n\n```ts\nconst { withState } = createStatefulMiddleware((input: { id: string }) =\u003e ({\n  id: input.id,\n}));\n\nconst workflow = withState(createWorkflow());\nconst { state } = workflow.createContext({ id: \"1\" });\n```\n\n`withState` also supports snapshot, you can use `snapshot` to save the state of the workflow, and `resume` to restore the state of the workflow.\n\n```ts\nconst { snapshot, resume } = workflow.createContext();\n\n// create snapshot\nconst snapshotData = await snapshot();\n\n// resume workflow from snapshot\nconst { stream, sendEvent } = workflow.resume(snapshotData);\nsendEvent(humanResponseEvent.with(\"hello\"));\n```\n\n### `withValidation`\n\nMake first parameter of `handler` to be `sendEvent` and its type safe and runtime safe\nwhen you create a workflow using `withValidation`.\n\n```ts\n// before:\nworkflow.handle([startEvent], (start) =\u003e {});\n// after:\nworkflow.handle([startEvent], (sendEvent, start) =\u003e {});\n```\n\n```ts\nimport { withValidation } from \"@llamaindex/workflow-core/middleware/validation\";\n\nconst startEvent = workflowEvent\u003cvoid, \"start\"\u003e();\nconst disallowedEvent = workflowEvent\u003cvoid, \"disallowed\"\u003e({\n  debugLabel: \"disallowed\",\n});\nconst parseEvent = workflowEvent\u003cstring, \"parse\"\u003e();\nconst stopEvent = workflowEvent\u003cnumber, \"stop\"\u003e();\nconst workflow = withValidation(createWorkflow(), [\n  [[startEvent], [stopEvent]],\n  [[startEvent], [parseEvent]],\n]);\n\nworkflow.strictHandle([startEvent], (sendEvent, start) =\u003e {\n  sendEvent(\n    disallowedEvent.with(), // \u003c-- ❌ Type Check Failed, Runtime Error\n  );\n  sendEvent(parseEvent.with(\"\")); // \u003c-- ✅\n  sendEvent(stopEvent.with(1)); // \u003c-- ✅\n});\n```\n\n### `withTraceEvents`\n\nAdds tracing capabilities to your workflow, allowing you to monitor/decorate handler and debug event flows easily.\n\nWhen enabled,\nit collects events based on the directed graph of the runtime and provide lifecycle hooks for each handler.\n\n```ts\nimport {\n  withTraceEvents,\n  runOnce,\n} from \"@llamaindex/workflow-core/middleware/trace-events\";\n\nconst workflow = withTraceEvents(createWorkflow());\n\nworkflow.handle(\n  [messageEvent],\n  runOnce(() =\u003e {\n    console.log(\"This message handler will only run once\");\n  }),\n);\n\nworkflow.handle([startEvent], (context) =\u003e {\n  context.sendEvent(messageEvent.with());\n  context.sendEvent(messageEvent.with());\n});\n\n{\n  const { sendEvent } = workflow.createContext();\n  sendEvent(startEvent.with());\n  sendEvent(messageEvent.with());\n  // This message handler will only run once!\n}\n{\n  const { sendEvent } = workflow.createContext();\n  // For each new context, the decorator is isolated.\n  sendEvent(startEvent.with());\n  sendEvent(messageEvent.with());\n  // This message handler will only run once!\n}\n```\n\n#### `workflow.substream(target, stream)`\n\nYou can use `substream` to create a substream from the workflow context,\nwhich will only emit events that are emitted by the target event.\n\n```ts\nconst ev = startEvent.with();\nconst { sendEvent, stream } = workflow.createContext();\nsendEvent(ev);\nsendEvent(messageEvent.with()); // \u003c- this will not be included in the substream\nconst substream = workflow.substream(ev, stream);\n```\n\nThis is helpful when you have async requests, and you want to track the events that are emitted by the target event.\n\nFor example:\n\n- Parallel requests\n\n  \u003cdetails\u003e\n    \u003csummary\u003ewithout substream\u003c/summary\u003e\n\n  ```ts\n  workflow.handle([startEvent], async (context, { data: uuid }) =\u003e {\n    const { sendEvent, stream } = context;\n    const ev = networkRequestEvent.with(uuid);\n    sendEvent(networkRequestEvent);\n    // you need bypass uuid to all events to get the correct response\n    const responses = await collect(\n      filter(workflow.substream(ev, stream), (ev) =\u003e ev.data === uuid),\n    );\n  });\n\n  sendEvent(startEvent.with(crypto.randomUUID()));\n  sendEvent(startEvent.with(crypto.randomUUID()));\n  ```\n\n  \u003c/details\u003e\n\n  ```ts\n  workflow.handle([startEvent], async (context) =\u003e {\n    const { sendEvent, stream } = context;\n    const ev = networkRequestEvent.with();\n    sendEvent(networkRequestEvent);\n    const responses = await collect(workflow.substream(ev, stream));\n  });\n\n  sendEvent(startEvent.with());\n  sendEvent(startEvent.with());\n  ```\n\n#### `createHandlerDecorator`\n\nYou can create your own handler decorator to modify the behavior of the handler.\n\n```ts\nimport { createHandlerDecorator } from \"@llamaindex/workflow-core/middleware/trace-events\";\n\nconst noop: (...args: any[]) =\u003e void = function noop() {};\nexport const runOnce = createHandlerDecorator({\n  debugLabel: \"onceHook\",\n  getInitialValue: () =\u003e false,\n  onBeforeHandler: (handler, handlerContext, tracked) =\u003e\n    tracked ? noop : handler,\n  onAfterHandler: () =\u003e true,\n});\n```\n\n#### `HandlerContext`\n\nThe `HandlerContext` includes the runtime information of the handler in the directed graph of the workflow.\n\n```ts\ntype BaseHandlerContext = {\n  // ... some other properties are hidden\n  handler: Handler\u003cWorkflowEvent\u003cany\u003e[], any\u003e;\n  inputEvents: WorkflowEvent\u003cany\u003e[];\n  // events data that are accepted by the handler\n  inputs: WorkflowEventData\u003cany\u003e[];\n  // events data that are emitted by the handler\n  outputs: WorkflowEventData\u003cany\u003e[];\n\n  //#region linked list data structure\n  prev: HandlerContext;\n  next: Set\u003cHandlerContext\u003e;\n  root: HandlerContext;\n  //#endregion\n};\n\ntype SyncHandlerContext = BaseHandlerContext \u0026 {\n  async: false;\n  pending: null;\n};\n\ntype AsyncHandlerContext = BaseHandlerContext \u0026 {\n  async: true;\n  pending: Promise\u003cWorkflowEventData\u003cany\u003e | void\u003e | null;\n};\n\ntype HandlerContext = AsyncHandlerContext | SyncHandlerContext;\n```\n\nFor example, when you send two `startEvent` events, and send `messageEvent` twice (once in the handler and once in the global),\nthe `HandlerContext` from root to leaf is:\n\n```ts\nlet once = false;\nworkflow.handle([startEvent], (context) =\u003e {\n  const { sendEvent } = context;\n  if (once) {\n    return;\n  }\n  once = true;\n  sendEvent(messageEvent.with());\n});\nconst { sendEvent } = workflow.createContext();\nsendEvent(startEvent.with());\nsendEvent(startEvent.with());\nsendEvent(messageEvent.with());\n```\n\n```\nrootHandlerContext(0)\n  ├── startEventContext(0)\n  │   └── messageEventContext(0)\n  ├── startEventContext(1)\n  └── messageEventContext(1)\n```\n\nYou can use any directed graph library to visualize the directed graph of the workflow.\n\n## Related Packages\n\n- [Python Workflows](https://github.com/run-llama/workflows-py)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frun-llama%2Fworkflows-ts","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frun-llama%2Fworkflows-ts","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frun-llama%2Fworkflows-ts/lists"}