______ _ _ ______ _ _ _ _ | ___ \ | | | | | ___| | | \ | | | | | |_/ /__ ___| | _____| |_| |_ | | _____ ________| \| | ___ __| | ___ | __/ _ \ / __| |/ / _ \ __| _| | |/ _ \ \ /\ / /______| . ` |/ _ \ / _` |/ _ \
| | | (_) | (__| < __/ |_| | | | (_) \ V V / | |\ | (_) | (_| | __/
\_| \___/ \___|_|\_\___|\__\_| |_|\___/ \_/\_/ \_| \_/\___/ \__,_|\___|
A minimalist Agentic LLM framework port of PocketFlow for TypeScript/Node.js
Lightweight: Just less than 300 lines. Zero bloat, zero dependencies, zero vendor lock-in.
Expressive: Everything you love—Agents, Workflows, RAG, Batch processing, and more.
Agentic Coding: Let AI Agents build Agents—10x productivity boost!
PocketFlow-Node is inspired by PocketFlow by Zachary Huang.
This TypeScript/Node.js version maintains the same minimalist philosophy and core abstractions as the original Python framework, bringing the power of agentic LLM development to the Node.js ecosystem.
npm install pocketflow-nodeOr install directly from GitHub:
npm install github:DavidNgugi/pocketflow-nodeimport{Node,Flow,SharedStore}from'pocketflow-node';// Define a simple nodeclassGreetNodeextendsNode{prep(shared: SharedStore){returnshared.name||'World';}exec(name: string){return`Hello, ${name}!`;}post(shared: SharedStore,prepRes: string,execRes: string){shared.greeting=execRes;console.log(execRes);}}// Create and run a flowconstgreetNode=newGreetNode();constflow=newFlow(greetNode);constshared: SharedStore={name: 'Alice'};flow.run(shared);// Output: Hello, Alice!// For more complex flows, use the natural syntax:constloadData=newLoadData();constprocessData=newProcessData();constsaveResult=newSaveResult();loadData.then(processData).then(saveResult);// OR// loadData >> processData >> saveResult;constcomplexFlow=newFlow(loadData);The smallest building block with three steps: prep → exec → post
classMyNodeextendsNode{prep(shared: SharedStore){// Read and preprocess datareturnshared.data;}exec(prepRes: any){// Execute compute logic (LLM calls, APIs, etc.)returnprocessData(prepRes);}post(shared: SharedStore,prepRes: any,execRes: any){// Write results back to shared storeshared.result=execRes;return'default';// Action to determine next node}}Orchestrates a graph of nodes with action-based transitions
constnodeA=newNodeA();constnodeB=newNodeB();constnodeC=newNodeC();// Connect nodes with natural English-like syntaxnodeA.then(nodeB);// Default transitionnodeA.on("error",nodeC);// Conditional transitionnodeA.onSuccess(nodeB);// Success pathnodeA.onError(nodeC);// Error handlingconstflow=newFlow(nodeA);flow.run(shared);.then(node)- Connect to next node on default/success.on(action, node)- Connect to node on specific action.onSuccess(node)- Connect to node on success action.onError(node)- Connect to node on error action.onRetry(node)- Connect to node on retry action
You can chain these methods for fluent, readable code:
loadData.then(validateData).then(processData).onError(handleError);Global data structure for communication between nodes
constshared: SharedStore={input: "Hello world",processed: null,result: null};classAsyncNodeextendsAsyncNode{asyncprepAsync(shared: SharedStore){returnawaitfetchData(shared.url);}asyncexecAsync(data: any){returnawaitcallLLM(data);}asyncpostAsync(shared: SharedStore,prepRes: any,execRes: any){shared.result=execRes;}}constasyncFlow=newAsyncFlow(asyncNode);awaitasyncFlow.runAsync(shared);classBatchProcessorextendsBatchNode{prep(shared: SharedStore){returnshared.items;// Array of items to process}exec(item: any){returnprocessItem(item);}post(shared: SharedStore,prepRes: any[],execRes: any[]){shared.results=execRes;}}classParallelProcessorextendsAsyncParallelBatchNode{asyncprepAsync(shared: SharedStore){returnshared.tasks;}asyncexecAsync(task: any){returnawaitexecuteTask(task);}}classAgentNodeextendsNode{exec(context: any){constaction=decideAction(context);returnaction;}post(shared: SharedStore,prepRes: any,execRes: any){returnexecRes.action;// 'search', 'answer', etc.}}// Connect agent nodesconstdecide=newDecideAction();constsearch=newSearchWeb();constanswer=newDirectAnswer();decide.on("search",search);decide.on("answer",answer);search.then(decide);// Loop back to decide// Offline: Index documentsconstchunk=newChunkDocs();constembed=newEmbedDocs();conststore=newStoreIndex();chunk.then(embed).then(store);constindexFlow=newFlow(chunk);// Online: Query and answerconstqueryEmbed=newEmbedQuery();constretrieve=newRetrieveDocs();constgenerate=newGenerateAnswer();queryEmbed.then(retrieve).then(generate);constqueryFlow=newFlow(queryEmbed);constoutline=newGenerateOutline();constwrite=newWriteContent();constreview=newReviewAndRefine();// Chain nodes in sequenceoutline.then(write).then(review);constwritingFlow=newFlow(outline);classRobustNodeextendsNode{constructor(){super(3,1000);// maxRetries=3, wait=1s}execFallback(prepRes: any,exc: Error){// Graceful fallback when all retries failreturn`Error processing: ${exc.message}`;}}Full TypeScript support with comprehensive type definitions:
import{BaseNode,Node,Flow,AsyncNode,AsyncFlow,SharedStore,Params,Action}from'pocketflow-node';Check out the examples directory for complete working examples:
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests
- Submit a pull request
MIT License - see LICENSE file for details.
- Discord: Join our Discord server
- Issues: Report bugs and request features on GitHub
- Discussions: Share ideas and ask questions in GitHub Discussions
- PocketFlow - Original Python version by Zachary Huang
- PocketFlow-Java - Java version
- PocketFlow-CPP - C++ version
- PocketFlow-Go - Go version
Built with ❤️ by the PocketFlow-Node community, inspired by Zachary Huang's PocketFlow.