______ _ _ ______ _ _ _ _
| ___ \ | | | | | ___| | | \ | | | |
| |_/ /__ ___| | _____| |_| |_ | | _____ ________| \| | ___ __| | ___
| __/ _ \ / __| |/ / _ \ __| _| | |/ _ \ \ /\ / /______| . ` |/ _ \ / _` |/ _ \
| | | (_) | (__| < __/ |_| | | | (_) \ 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 node
class GreetNode extends Node {
prep(shared: SharedStore) {
return shared.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 flow
const greetNode = new GreetNode();
const flow = new Flow(greetNode);
const shared: SharedStore = { name: 'Alice' };
flow.run(shared);
// Output: Hello, Alice!
// For more complex flows, use the natural syntax:
const loadData = new LoadData();
const processData = new ProcessData();
const saveResult = new SaveResult();
loadData.then(processData).then(saveResult);
// OR
// loadData >> processData >> saveResult;
const complexFlow = new Flow(loadData);The smallest building block with three steps: prep → exec → post
class MyNode extends Node {
prep(shared: SharedStore) {
// Read and preprocess data
return shared.data;
}
exec(prepRes: any) {
// Execute compute logic (LLM calls, APIs, etc.)
return processData(prepRes);
}
post(shared: SharedStore, prepRes: any, execRes: any) {
// Write results back to shared store
shared.result = execRes;
return 'default'; // Action to determine next node
}
}Orchestrates a graph of nodes with action-based transitions
const nodeA = new NodeA();
const nodeB = new NodeB();
const nodeC = new NodeC();
// Connect nodes with natural English-like syntax
nodeA.then(nodeB); // Default transition
nodeA.on("error", nodeC); // Conditional transition
nodeA.onSuccess(nodeB); // Success path
nodeA.onError(nodeC); // Error handling
const flow = new Flow(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
const shared: SharedStore = {
input: "Hello world",
processed: null,
result: null
};class AsyncNode extends AsyncNode {
async prepAsync(shared: SharedStore) {
return await fetchData(shared.url);
}
async execAsync(data: any) {
return await callLLM(data);
}
async postAsync(shared: SharedStore, prepRes: any, execRes: any) {
shared.result = execRes;
}
}
const asyncFlow = new AsyncFlow(asyncNode);
await asyncFlow.runAsync(shared);class BatchProcessor extends BatchNode {
prep(shared: SharedStore) {
return shared.items; // Array of items to process
}
exec(item: any) {
return processItem(item);
}
post(shared: SharedStore, prepRes: any[], execRes: any[]) {
shared.results = execRes;
}
}class ParallelProcessor extends AsyncParallelBatchNode {
async prepAsync(shared: SharedStore) {
return shared.tasks;
}
async execAsync(task: any) {
return await executeTask(task);
}
}class AgentNode extends Node {
exec(context: any) {
const action = decideAction(context);
return action;
}
post(shared: SharedStore, prepRes: any, execRes: any) {
return execRes.action; // 'search', 'answer', etc.
}
}
// Connect agent nodes
const decide = new DecideAction();
const search = new SearchWeb();
const answer = new DirectAnswer();
decide.on("search", search);
decide.on("answer", answer);
search.then(decide); // Loop back to decide// Offline: Index documents
const chunk = new ChunkDocs();
const embed = new EmbedDocs();
const store = new StoreIndex();
chunk.then(embed).then(store);
const indexFlow = new Flow(chunk);
// Online: Query and answer
const queryEmbed = new EmbedQuery();
const retrieve = new RetrieveDocs();
const generate = new GenerateAnswer();
queryEmbed.then(retrieve).then(generate);
const queryFlow = new Flow(queryEmbed);const outline = new GenerateOutline();
const write = new WriteContent();
const review = new ReviewAndRefine();
// Chain nodes in sequence
outline.then(write).then(review);
const writingFlow = new Flow(outline);class RobustNode extends Node {
constructor() {
super(3, 1000); // maxRetries=3, wait=1s
}
execFallback(prepRes: any, exc: Error) {
// Graceful fallback when all retries fail
return `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.