Skip to content

A config-driven LLM framework built on top of PocketFlow. BackpackFlow extends PocketFlow with configuration-driven workflows, utility functions, and enhanced developer experience.

License

Notifications You must be signed in to change notification settings

pyrotank41/BackpackFlow

Repository files navigation

πŸŽ’ BackpackFlow

A TypeScript-first, config-driven LLM framework built on top of PocketFlow.

BackpackFlow extends PocketFlow with a specific philosophy: The Code is the Engine, the Config is the Steering Wheel.

npm version License: Apache 2.0

⚑ v2.0 "The Observable Agent" - Build production-ready AI agents with complete observability, Zod-based type safety, and nested flow composition. TypeScript-first, config-driven, and ready for visual builders.


🚫 The Pain Points (Why BackpackFlow Exists)

Most LLM development hits three major walls:

1. The "Black Box" State

In many frameworks, context (history, variables) is handled by "magic." You don't know exactly what the LLM can "see" at any given step. Debugging feels like "doing animal experiments."

2. The "No-Code" Wall

Visual builders are great for demos, but when you need complex loops or custom logic, you hit a wall. You can't "eject" to code easily, and your flow is trapped in the GUI.

3. The Language Barrier

Python is great for data science, but if you want to build a web-based tracer or a drag-and-drop UI, you end up duplicating types between your Python backend and React frontend.


πŸ’‘ The BackpackFlow Solution

We solve these pain points with a TypeScript-First, Config-Driven architecture.

1. "Git for Your Agent's State" (Solves Black Box State)

Think of Backpack as "Git for your agent's memory."

Just like Git tracks every code change with commits, Backpack tracks every data change in your agent:

graph LR
    subgraph Git["πŸ”§ Git (Code Versioning)"]
        G1["git commit"] 
        G2["git log"]
        G3["git checkout abc123"]
        G4["git diff"]
    end
    
    subgraph Backpack["πŸŽ’ Backpack (State Versioning)"]
        B1["backpack.pack('key', value)"]
        B2["backpack.getHistory()"]
        B3["backpack.getSnapshot()"]
        B4["backpack.diff(before, after)"]
    end
    
    G1 -.->|"Same Concept"| B1
    G2 -.->|"Same Concept"| B2
    G3 -.->|"Same Concept"| B3
    G4 -.->|"Same Concept"| B4
    
    style Git fill:#f0f0f0,stroke:#333,stroke-width:2px
    style Backpack fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
Loading

Why "Backpack"? Because your agent carries explicit data from node to node:

  • πŸŽ’ Nothing is hidden - if it's not in the Backpack, the agent can't use it
  • πŸ” Every item is tagged with who packed it, when, and why
  • 🚫 Nodes declare access permissions - can't accidentally read debug data or PII
  • ⏱️ Complete audit trail - trace any data back to its source

The Result: Instead of debugging "black box" state mutations, you have:

  • βœ… Immutable History - Every data change is tracked (like Git commits)
  • βœ… Time-Travel Debugging - Rewind to any previous state (git checkout)
  • βœ… Complete Auditability - Know exactly who changed what, when (git blame)
  • βœ… Access Control - Nodes declare what they can read/write (unlike SharedStore)

If Git made code development manageable, Backpack makes agent development manageable.

2. Code-First, UI-Ready (Solves the No-Code Wall)

We are building a "bridge" where Code and Config are interchangeable.

  • The Engine: You write complex logic in TypeScript Nodes
  • The Steering Wheel: The framework serializes your Nodes into JSON Config
  • The Result: Build a UI Layer that can visualize and edit your flow, but allows you to "eject" to raw code whenever needed
graph LR
    A[TypeScript Code] -->|Compiles to| B(The Engine)
    A -->|Serializes to| C{JSON Config}
    C -->|Hydrates| B
    C <-->|Syncs with| D[Future Web GUI]
    style A fill:#f9f,stroke:#333,stroke-width:2px
    style D fill:#bbf,stroke:#333,stroke-width:2px
Loading

3. TypeScript-First (Solves the Language Barrier)

Build your backend logic AND your web UI in the same language. Share types, schemas, and validation logic seamlessly.


πŸ“ Current Version: v2.0.0

"The Observable Agent" - Complete rewrite with production-ready observability

  • Architecture: Git-like state management with immutable history
  • Type Safety: Full Zod schema validation with type inference
  • Observability: Automatic event emission and time-travel debugging
  • Composition: Nested flows with recursive serialization
  • Config-Driven: Complete JSON serialization for visual builders

πŸ‘‰ See Full Roadmap | Migration from v1.x

✨ Features

Core Architecture (v2.0)

πŸŽ’ Backpack: Git-Like State Management

πŸ“š Documentation

Think of it as "Git for your agent's memory" - every data change is tracked with full history:

  • Immutable History: Every state change recorded like Git commits
  • Time-Travel Debugging: Rewind to any previous state to see what the agent "knew"
  • Source Tracking: Know exactly which node added/modified each piece of data
  • Access Control: Nodes declare what they can read/write with wildcard support
  • State Quarantine: Isolate failed operations from downstream nodes
%%{init: {'theme':'base'}}%%
timeline
    title Backpack State History (Like Git Log)
    section Node A
        Commit 1 : pack('query', 'AI agents')
    section Node B
        Commit 2 : pack('results', [...])
        Commit 3 : pack('filtered', [...])
    section Node C
        Commit 4 : pack('summary', 'Analysis...')
    section Time-Travel
        Checkpoint : getSnapshot() β†’ Rewind to any point
Loading

πŸ“‘ Event Streaming: Complete Observability

πŸ“š Documentation

Automatic event emission for every node lifecycle event - no manual logging needed:

  • 5 Event Types: NODE_START, PREP_COMPLETE, EXEC_COMPLETE, NODE_END, ERROR
  • Prompt Inspection: See exact LLM prompts via PREP_COMPLETE events
  • Parse Error Visibility: Inspect raw responses before JSON parsing fails
  • Namespace Filtering: Subscribe to events with wildcard patterns
  • Event History: Built-in event storage for post-mortem debugging
graph LR
    A[NODE_START] --> B[PREP_COMPLETE]
    B --> C[EXEC_COMPLETE]
    C --> D[NODE_END]
    
    B -.->|"Error in prep()"| E[ERROR]
    C -.->|"Error in _exec()"| E
    
    B -->|"πŸ“‹ Emits"| B1["Input data<br/>LLM prompts"]
    C -->|"πŸ“‹ Emits"| C1["Execution result<br/>Raw LLM response"]
    D -->|"πŸ“‹ Emits"| D1["Final action<br/>Duration"]
    E -->|"πŸ“‹ Emits"| E1["Error details<br/>Stack trace"]
    
    style A fill:#4caf50,stroke:#333,color:#fff
    style D fill:#2196f3,stroke:#333,color:#fff
    style E fill:#f44336,stroke:#333,color:#fff
    style B1 fill:#fff3cd,stroke:#856404
    style C1 fill:#fff3cd,stroke:#856404
    style D1 fill:#d1ecf1,stroke:#0c5460
    style E1 fill:#f8d7da,stroke:#721c24
Loading

πŸ”Œ Config-Driven Architecture

πŸ“š Documentation

Bidirectional conversion between TypeScript code and JSON configs:

  • JSON Serialization: Export complete flows to JSON for storage/transfer
  • Type-Safe Loading: Zod-validated configs prevent runtime errors
  • Dependency Injection: Clean handling of non-serializable objects (LLM clients, DBs)
  • Round-Trip Guarantee: fromConfig(toConfig()) preserves node identity
  • UI-Ready: Foundation for drag-and-drop flow builders
graph LR
    subgraph Code["πŸ’» TypeScript Code"]
        Node["class MyNode extends BackpackNode {<br/>  params = {...}<br/>  async _exec() {...}<br/>}"]
    end
    
    subgraph Config["πŸ“„ JSON Config"]
        JSON["{<br/>  type: 'MyNode',<br/>  id: 'node1',<br/>  params: {...},<br/>  internalFlow: {...}<br/>}"]
    end
    
    subgraph UI["🎨 Visual Builder"]
        Drag["Drag & Drop<br/>Flow Editor"]
    end
    
    Node -->|"toConfig()"| JSON
    JSON -->|"fromConfig()"| Node
    JSON <-->|"Edit/View"| UI
    
    Check["βœ… Round-Trip Verified:<br/>Node identity preserved"]
    JSON -.-> Check
    Node -.-> Check
    
    style Code fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
    style Config fill:#e8f5e9,stroke:#388e3c,stroke-width:2px
    style UI fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
    style Check fill:#c8e6c9,stroke:#388e3c
Loading

πŸ”€ Nested Flows & Composition

πŸ“š Documentation

Build complex agents from reusable components with standard patterns:

  • createInternalFlow(): Auto-wiring of namespace, backpack, and events
  • Recursive Serialization: Complete nested structure in JSON
  • Convenience Methods: .onComplete(), .onError() instead of string-based routing
  • FlowAction Enum: Type-safe routing with standardized actions
  • Query API: flattenNodes(), findNode(), getMaxDepth() for flow introspection
graph TB
    Entry["πŸš€ Entry Node"] --> Agent
    
    subgraph Agent["πŸ“¦ YouTubeResearchAgent (Composite Node)"]
        direction TB
        AgentStart["▢️ _exec() called"] --> CreateFlow["createInternalFlow()"]
        
        subgraph InternalFlow["πŸ”— Internal Flow<br/>(Auto-wired: namespace, backpack, eventStreamer)"]
            direction LR
            Search["πŸ” YouTube<br/>Search"] -->|"onComplete()"| Analysis["πŸ“Š Data<br/>Analysis"]
            Analysis -->|"onComplete()"| Summary["πŸ€– LLM<br/>Summary"]
        end
        
        CreateFlow --> InternalFlow
        InternalFlow --> AgentComplete["βœ… return 'complete'"]
    end
    
    Agent --> Exit["🎯 Exit Node"]
    
    style Entry fill:#4caf50,stroke:#2e7d32,stroke-width:2px,color:#fff
    style Agent fill:#fff59d,stroke:#f57f17,stroke-width:3px
    style InternalFlow fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
    style Search fill:#4fc3f7,stroke:#0277bd,stroke-width:2px
    style Analysis fill:#81c784,stroke:#388e3c,stroke-width:2px
    style Summary fill:#ffb74d,stroke:#e65100,stroke-width:2px
    style AgentStart fill:#e0e0e0,stroke:#616161
    style CreateFlow fill:#e0e0e0,stroke:#616161
    style AgentComplete fill:#c8e6c9,stroke:#388e3c,stroke-width:2px
    style Exit fill:#2196f3,stroke:#0d47a1,stroke-width:2px,color:#fff
Loading

πŸ” Data Contracts & Type Safety

πŸ“š Documentation

Zod-powered input/output contracts for bulletproof type safety:

  • Explicit Contracts: Nodes declare expected inputs and outputs with Zod schemas
  • Runtime Validation: Automatic validation with detailed error messages
  • Type Inference: Full TypeScript types inferred from schemas
  • Data Mappings: Edge-level key remapping for flexible composition
  • JSON Schema Export: Generate schemas for UI form builders
graph LR
    subgraph NodeA["πŸ”· Search Node"]
        A1["outputs: {<br/>  query: z.string()<br/>  results: z.array(...)<br/>}"]
    end
    
    subgraph Edge["πŸ”— Edge Mapping"]
        E1["mappings: {<br/>  results β†’ data<br/>}"]
    end
    
    subgraph NodeB["πŸ”Ά Analysis Node"]
        B1["inputs: {<br/>  data: z.array(...)<br/>}"]
        B2["βœ… Validated!"]
    end
    
    NodeA -->|"results"| Edge
    Edge -->|"data"| NodeB
    B1 --> B2
    
    style NodeA fill:#e3f2fd,stroke:#1976d2
    style NodeB fill:#fff3e0,stroke:#e65100
    style Edge fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
    style B2 fill:#c8e6c9,stroke:#388e3c
Loading

Project Structure

backpackflow/
β”œβ”€β”€ src/                    # Source code
β”‚   β”œβ”€β”€ pocketflow.ts      # PocketFlow core (ported)
β”‚   └── index.ts           # Main entry point
β”œβ”€β”€ tests/                  # Test files
β”œβ”€β”€ tutorials/              # Learning guides and examples
β”œβ”€β”€ dist/                   # Compiled output
└── docs/                   # Documentation

Installation

npm install backpackflow zod

Dependencies:

  • zod - Required for data contracts and validation (v2.0+)

Quick Start

1. Create a Simple Node with Data Contracts

sequenceDiagram
    participant F as Flow
    participant N as Node
    participant B as Backpack
    participant E as EventStreamer
    
    F->>N: Start execution
    N->>E: Emit NODE_START
    
    rect rgb(230, 240, 255)
        Note over N,B: prep() - Read inputs
        N->>B: unpack('name')
        B-->>N: 'World'
        N->>E: Emit PREP_COMPLETE
    end
    
    rect rgb(240, 255, 230)
        Note over N: _exec() - Process
        N->>N: Execute logic
        N->>E: Emit EXEC_COMPLETE
    end
    
    rect rgb(255, 240, 230)
        Note over N,B: post() - Write outputs
        N->>B: pack('greeting', result)
        B-->>B: Commit to history
        N->>E: Emit NODE_END
    end
    
    N-->>F: Return action ('complete')
Loading
import { BackpackNode, NodeConfig, NodeContext } from 'backpackflow';
import { z } from 'zod';

// Define your node with Zod contracts
class GreetingNode extends BackpackNode {
    static namespaceSegment = "greeting";
    
    // ✨ Explicit input/output contracts
    static inputs = {
        name: z.string().min(1).describe('User name')
    };
    
    static outputs = {
        greeting: z.string().describe('Generated greeting')
    };
    
    async prep(shared: any) {
        return this.unpackRequired('name'); // Runtime validation!
    }
    
    async _exec(name: string) {
        return `Hello, ${name}! Welcome to BackpackFlow v2.0!`;
    }
    
    async post(shared: any, prep: any, greeting: string) {
        this.pack('greeting', greeting); // Tracked in Backpack history
        return 'complete';
    }
}

2. Build a Flow with Event Streaming

import { Flow, Backpack, EventStreamer } from 'backpackflow';

// Create observable flow
const backpack = new Backpack({});
const eventStreamer = new EventStreamer({ enableHistory: true });

const flow = new Flow({ 
    namespace: 'demo', 
    backpack, 
    eventStreamer 
});

// Add node
const greetNode = flow.addNode(GreetingNode, { id: 'greet' });

// Listen to events
eventStreamer.on('*', (event) => {
    console.log(`[${event.type}] ${event.nodeName}`);
});

// Pack input and run
backpack.pack('name', 'World');
await flow.run({});

// Access result
const greeting = backpack.unpack('greeting');
console.log(greeting); // "Hello, World! Welcome to BackpackFlow v2.0!"

3. Time-Travel Debugging

// Get complete history
const history = backpack.getHistory();
console.log('All state changes:', history);

// Get snapshot at specific point
const snapshot = backpack.getSnapshot();
console.log('Current state:', snapshot);

// Diff between states
const before = backpack.getSnapshot();
// ... make changes ...
const after = backpack.getSnapshot();
const diff = backpack.diff(before, after);
console.log('What changed:', diff);

4. Serialize to JSON

import { FlowLoader } from 'backpackflow/serialization';

const loader = new FlowLoader();
loader.register('GreetingNode', GreetingNode);

// Export complete flow structure
const config = loader.exportFlow(flow);
console.log(JSON.stringify(config, null, 2));

// Load from config
const loadedFlow = await loader.loadFlow(config, deps);

Development

# Install dependencies
npm install

# Build the project
npm run build

# Run tests
npm test

# Run a tutorial
npx ts-node tutorials/youtube-research-agent/youtube-research-agent.ts

πŸŽ“ Learning & Examples

Featured Example: YouTube Research Agent

tutorials/youtube-research-agent/ - Production-ready agent showcasing all v2.0 features:

class YouTubeResearchAgentNode extends BackpackNode {
    async _exec(input: any) {
        // ✨ Create internal flow with auto-wiring
        const flow = this.createInternalFlow();
        
        const searchNode = flow.addNode(YouTubeSearchNode, { id: 'search' });
        const analysisNode = flow.addNode(DataAnalysisNode, { id: 'analysis' });
        const summaryNode = flow.addNode(BaseChatCompletionNode, { id: 'summary' });
        
        // ✨ Clean routing with convenience methods
        searchNode.onComplete(analysisNode);
        analysisNode.onComplete(summaryNode);
        
        await flow.run({});
    }
}
graph LR
    User["πŸ‘€ User<br/>'AI agents'"] --> Search["πŸ” YouTube Search<br/><br/>Contract:<br/>in: query<br/>out: videos[]"]
    
    Search -->|"onComplete()"| Analysis["πŸ“Š Data Analysis<br/><br/>Contract:<br/>in: videos[]<br/>out: statistics"]
    
    Analysis -->|"onComplete()"| Summary["πŸ€– LLM Summary<br/><br/>Contract:<br/>in: statistics<br/>out: summary"]
    
    Summary --> Result["πŸ“„ Result<br/>'Found 45 videos...'"]
    
    subgraph Events["πŸ“‘ Event Stream"]
        E1["NODE_START Γ— 3"]
        E2["PREP_COMPLETE Γ— 3"]
        E3["EXEC_COMPLETE Γ— 3"]
        E4["NODE_END Γ— 3"]
    end
    
    Search -.->|"Emits"| Events
    Analysis -.->|"Emits"| Events
    Summary -.->|"Emits"| Events
    
    style Search fill:#4fc3f7,stroke:#0277bd,stroke-width:2px
    style Analysis fill:#81c784,stroke:#388e3c,stroke-width:2px
    style Summary fill:#ffb74d,stroke:#e65100,stroke-width:2px
    style Events fill:#f3e5f5,stroke:#7b1fa2
    style User fill:#fff9c4,stroke:#f57f17
    style Result fill:#c8e6c9,stroke:#388e3c
Loading

Features demonstrated:

  • πŸ”€ Composite nodes with nested flows
  • βœ… Zod-based data contracts with type inference
  • πŸ“‘ Event streaming with hierarchical visualization
  • πŸ’Ύ Complete flow serialization to JSON
  • 🎯 Channel-relative outlier detection algorithm

Additional Tutorials

Advanced Patterns:

Legacy Examples (v1.x):

See the tutorials/ directory for all examples.

πŸ“‹ What's New

v2.0.0 "The Observable Agent" (Current)

Major architectural rewrite with production-grade observability and type safety.

graph TB
    subgraph Flow["πŸ”„ Flow Orchestration"]
        N1[Node A] -->|"onComplete()"| N2[Node B]
        N2 --> N3[Node C]
    end
    
    subgraph Backpack["πŸŽ’ Backpack (State)"]
        H1["πŸ“œ Immutable History"]
        H2["🎯 Access Control"]
        H3["⏱️ Time Travel"]
    end
    
    subgraph Events["πŸ“‘ Event Streaming"]
        E1["NODE_START"]
        E2["EXEC_COMPLETE"]
        E3["ERROR"]
    end
    
    subgraph Serialization["πŸ’Ύ Serialization"]
        S1["toConfig() β†’ JSON"]
        S2["fromConfig() β†’ Code"]
    end
    
    subgraph Contracts["πŸ” Data Contracts"]
        C1["Zod Validation"]
        C2["Edge Mappings"]
    end
    
    N1 -.->|"pack()/unpack()"| Backpack
    N2 -.->|"pack()/unpack()"| Backpack
    N3 -.->|"pack()/unpack()"| Backpack
    
    N1 -.->|"emit"| Events
    N2 -.->|"emit"| Events
    N3 -.->|"emit"| Events
    
    Flow -->|"exportFlow()"| Serialization
    Serialization -->|"loadFlow()"| Flow
    
    N1 --> Contracts
    N2 --> Contracts
    N3 --> Contracts
    
    style Flow fill:#e3f2fd,stroke:#1976d2,stroke-width:3px
    style Backpack fill:#fff3e0,stroke:#e65100,stroke-width:2px
    style Events fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
    style Serialization fill:#e8f5e9,stroke:#388e3c,stroke-width:2px
    style Contracts fill:#fce4ec,stroke:#c2185b,stroke-width:2px
Loading

🎯 Core Systems

Backpack Architecture

  • Git-like state management with immutable commit history
  • Time-travel debugging with state snapshots
  • Fine-grained access control with namespace wildcards
  • State quarantine for isolating failed operations

Event Streaming

  • 5 standardized event types for complete lifecycle visibility
  • Automatic emission - zero manual logging required
  • Namespace-based filtering with wildcard support
  • Built-in event history for debugging

Config-Driven Serialization

  • Bidirectional TypeScript ↔ JSON conversion
  • Zod-powered validation for type safety
  • Dependency injection for non-serializable objects
  • Round-trip guarantee for config preservation

Nested Flows & Composition

  • createInternalFlow() with automatic context inheritance
  • Recursive serialization for complete flow structure
  • .onComplete() / .onError() convenience methods
  • Query utilities for flow introspection

Zod Data Contracts

  • Explicit input/output declarations on nodes
  • Runtime validation with detailed error messages
  • Full TypeScript type inference
  • Edge-level data mappings for key remapping

πŸ”§ Developer Experience

  • Type Safety: End-to-end TypeScript with Zod schema validation
  • Observability: See everything - prompts, responses, state changes, errors
  • Debugging: Time-travel to any point in execution history
  • Composition: Build complex agents from simple, reusable nodes
  • UI-Ready: Complete serialization for visual flow builders

πŸ“– Resources


Previous Versions

v1.2.0 - Event-Driven Architecture (Legacy)
  • Explicit LLM client injection
  • Enhanced event streaming with StreamEventType enum
  • Azure OpenAI support
  • Improved AgentNode with better defaults
v1.1.0 - Event-Driven Streaming (Legacy)
  • EventStreamer for centralized event management
  • Real-time streaming support
  • High-level AgentNode orchestration
v1.0.x - Initial Release (Legacy)
  • Basic PocketFlow integration
  • OpenAI provider integration
  • Core node types (Chat, Decision, utilities)

🀝 Join the Community

Want to contribute, get help, or share what you're building?

πŸ‘‰ Join our community - Connect with other developers building AI applications

πŸ› οΈ Contributing

Contributions are welcome! BackpackFlow v2.0 is feature-complete, but there's always room for:

  • πŸ› Bug fixes and improvements
  • πŸ“š Documentation enhancements
  • 🎯 More example agents and tutorials
  • πŸ§ͺ Additional test coverage
  • πŸš€ Performance optimizations

Architecture Overview

Want to contribute? Start by understanding the core systems:

  1. Backpack Architecture - State management with Git-like history
  2. Event Streaming - Observability and telemetry
  3. Serialization Bridge - Config-driven flows
  4. Composite Nodes - Nested flow composition
  5. Flow Observability - Data contracts and mappings

Development Workflow

  1. Fork the repository
  2. Create a feature branch (git checkout -b feat/amazing-feature)
  3. Make your changes
  4. Run tests (npm test)
  5. Build the project (npm run build)
  6. Submit a pull request

πŸ‘‰ See the Roadmap for planned features and improvements.

License

Apache-2.0 - see the LICENSE file for details.

Copyright 2024 BackpackFlow

About

A config-driven LLM framework built on top of PocketFlow. BackpackFlow extends PocketFlow with configuration-driven workflows, utility functions, and enhanced developer experience.

Resources

License

Stars

Watchers

Forks

Packages

No packages published