Quick Start

Welcome to Happen, a framework for building agentic systems founded on a philosophy of simplicity. Unlike conventional frameworks that burden developers with complex abstractions, Happen provides just two fundamental building blocks that can be composed to create systems ranging from simple pipelines to complex, adaptive multi-agent ecosystems.

Happen distills agent-based systems down to their essence, recognizing that true power emerges from simplicity rather than complexity. At its foundation lie just two primitives:

  1. Nodes - Independent, autonomous components that process and respond to information

  2. Events - Structured messages that transport data and intentions between Nodes

This deliberate minimalism creates a framework that is both accessible to newcomers and powerful enough for experts, allowing developers to focus on solving domain problems rather than battling framework complexities.

Installation

Current Development Phase

Happen is currently in active development and not yet published to NPM. To get started, you'll need to clone the repository directly from GitHub.

Getting Started

Clone the Happen repository and set up your development environment:

# Clone the Happen repository
git clone https://github.com/RobAntunes/Happen.git happen    

# Navigate to the project directory
cd happen

# Install dependencies
npm install

# Build the project
npm run build

# Run tests to verify installation
npm test

Quick Setup Example

Once you've cloned and built Happen, you can start using it immediately:

// Import Happen after cloning and building
import { initializeHappen } from './happen/dist/index.js';

// Initialize with default configuration
const { createNode } = initializeHappen();

// Create your first node
const myNode = createNode('my-first-node');

// Set up a simple event handler
myNode.on('hello', (event) => {
  console.log('Received:', event.payload);
  return { success: true };
});

// Send an event
myNode.broadcast({
  type: 'hello',
  payload: { message: 'Hello, Happen!' }
});

Coming Soon

Happen will be available via NPM once we reach our first stable release. Stay tuned for:

# Future NPM installation (coming soon)
npm install happen

Note: Make sure you have Node.js 18+ installed for optimal compatibility.

Setup and Initialization

To get started with Happen, first initialize the framework with your desired configuration:

// Initialize Happen with NATS as the communication fabric
const happen = initializeHappen({
  // NATS configuration
  nats: {
    // Connection configuration
    connection: {
      // Server environment (direct NATS)
      server: {
        servers: ['nats://localhost:4222'],
        jetstream: true
      },
      // Browser environment (WebSocket)
      browser: {
        servers: ['wss://localhost:8443'],
        jetstream: true
      }
    }
  }
});

// Extract the createNode function
const { createNode } = happen;

For simpler use cases, you can use the default configuration:

// Use default configuration with local NATS server
const { createNode } = initializeHappen();

Pure Causality: The Secret to Happen's Power

The most distinctive aspect of Happen is its embrace of pure causality as its organizing principle. This approach eliminates entire categories of complexity found in other systems.

In Happen:

  • Events can only be created when their prerequisites exist

  • Every event contains references to its causal predecessors

  • Each event has a unique identifier that gets referenced by its dependents

  • These references form a complete causal web that defines system behavior

This means that knowledge about the system is encoded directly in event flow patterns. Nodes don't need schema definitions or formal contracts to understand how to interact - they simply need to observe the event flow.

The Event Continuum: Programmable Flow Control

One of Happen's most powerful features is the "Event Continuum" model, which gives you complete control over event handling and flow transitions. Unlike typical event handlers that simply execute and complete, Happen handlers can control what happens next:

// Register an event handler - the first step in processing
orderNode.on("process-order", function validateOrder(event, context) {
  // Validate the order
  const validation = validateOrderData(event.payload);
  
  if (!validation.valid) {
    // Return a value to complete the flow with an error
    return {
      success: false,
      reason: "validation-failed",
      errors: validation.errors
    };
  }
  
  // Store validation result in context for later steps
  context.validation = validation;
  
  // Return the next function to execute - controlling the transition
  return processPayment;
});

// The next function in the flow
function processPayment(event, context) {
  // Access data from previous step through context
  const validatedOrder = context.validation;
  
  // Process payment
  const paymentResult = processTransaction(validatedOrder);
  
  if (!paymentResult.success) {
    // Return a value to complete with failure
    return {
      success: false,
      reason: "payment-failed"
    };
  }
  
  // Store payment result in context
  context.payment = paymentResult;
  
  // Return the next function to transition to
  return createShipment;
}

// Final step in the flow
function createShipment(event, context) {
  // Access data from previous steps
  const { payment, validation } = context;
  
  // Create shipment
  const shipment = generateShipment(validation.address);
  
  // Return final result to complete the flow
  return {
    success: true,
    orderId: validation.orderId,
    paymentId: payment.transactionId,
    trackingNumber: shipment.trackingNumber
  };
}

This functional flow approach means:

  1. Explicit Transitions: You control what happens next by returning either a value (to complete) or a function (to continue)

  2. Shared Context: Information flows between steps through the context object

  3. Natural Branching: Flow can branch based on conditions without complex routing rules

  4. Composable Workflows: Complex flows emerge from simple, focused functions

The Event Continuum model enables remarkably clean and readable code for complex workflows while maintaining full control over transitions.

The Causal Event Web

When a node receives this event, it automatically knows:

// An event naturally references its causal predecessors
{
  type: 'order-shipped',
  payload: {
    orderId: 'ORD-123',
    trackingNumber: 'TRK-456'
  },
  context: {
    // ...
    causal: {
      id: 'evt-789', // Unique identifier
      sender: 'shipping-node', // Origin node
      causationId: 'evt-456', // Direct cause (payment confirmation)
      correlationId: 'order-123' // Overall transaction
    }
  }
}
  • This event was caused by event 'evt-456'

  • It's part of the transaction 'order-123'

  • It originated from 'shipping-node'

The entire history and context are embedded in the event itself.

This pure causality model means:

  • No need for schema registries

  • No explicit contract definitions

  • No capability discovery protocols

  • No separate identity systems

The system self-describes through its natural operation, with each event reinforcing the web of causality that defines how components interact.

Building Your First Happen System

Let's create a simple order processing system to demonstrate Happen's principles in action:

First, we'll create autonomous nodes to handle different aspects of our order process:

// Create independent nodes
const orderNode = createNode('order-service');
const inventoryNode = createNode('inventory-service');
const paymentNode = createNode('payment-service');
const shippingNode = createNode('shipping-service');

Next, we'll define how each node responds to events using the Event Continuum:

// Order node handles order creation with flow control
orderNode.on('create-order', function validateOrder(event, context) {
  // Validate order data
  const validation = validateOrderData(event.payload);
  
  if (!validation.valid) {
    return {
      success: false,
      errors: validation.errors
    };
  }
  
  // Generate order ID
  const orderId = generateOrderId();
  
  // Transition to inventory check, passing data through context
  context.orderData = {
    id: orderId,
    items: event.payload.items,
    customer: event.payload.customer
  };
  
  return checkInventory;
});

// Additional flow functions defined here...

Finally, we'll initialize the system and process an example order:

function startOrderSystem() {
  console.log('Order processing system initialized and ready');
  
  // Broadcast an order creation event to start the process
  orderNode.broadcast({
    type: 'create-order',
    payload: {
      customer: {
        id: 'cust-123',
        name: 'Jane Doe',
        email: 'jane@example.com',
        address: {
          street: '123 Main St',
          city: 'Anytown',
          zip: '12345'
        }
      },
      items: [
        { id: 'prod-a1', quantity: 2 },
        { id: 'prod-b7', quantity: 1 }
      ]
    }
  });
  
  // The system now autonomously processes this event through the flow:
  // 1. validateOrder function runs and transitions to checkInventory
  // 2. checkInventory function runs and transitions to processPayment
  // 3. processPayment function runs and transitions to createShipment
  // 4. createShipment function completes the flow and returns a result
  // All steps share context and control their own transitions
}

// Start the system
startOrderSystem();

Communication Patterns

Happen provides two communication patterns that can be composed to create any interaction model:

1. System-wide Broadcasting

Events are sent to all nodes in the system:

// Weather service broadcasts a system-wide alert
weatherNode.broadcast({
  type: 'severe-weather',
  payload: {
    condition: 'hurricane',
    region: 'Gulf Coast'
  }
});

// Any node can respond
emergencyNode.on('severe-weather', event => {
  deployEmergencyTeams(event.payload.region);
});

2. Direct Communication

Point-to-point request-response interactions:

// Direct request with awaited response
const response = await orderNode.send(paymentNode, {
  type: 'process-payment',
  payload: {
    orderId: 'ORD-28371',
    amount: 129.99
  }
});

// Handle the response
if (response.success) {
  completeOrder(response.orderId);
}

The receiving node knows to return a value because the event context automatically includes information about the sender and whether a response is expected:

// Payment node handling direct communication
paymentNode.on('process-payment', (event, context) => {
  // The context contains information about the sender and request
  console.log(context.sender); // 'order-service'
  console.log(context.expectsResponse); // true
  
  // Process the payment
  const result = processPayment(event.payload.orderId, event.payload.amount);
  
  // Simply returning a value sends it back to the requester
  return {
    success: result.success,
    transactionId: result.success ? result.transactionId : null,
    status: result.success ? 'approved' : 'declined',
    message: result.message
  };
});

This simple mechanism means that nodes don't need to know anything special about handling direct communication - they just return values from their handlers as usual. The Happen framework takes care of routing the response back to the sender when a response is expected.

Distributed Operation

With NATS as the underlying messaging fabric, your Happen system automatically works across different processes, machines, and environments. The same code works whether nodes are:

  • In the same process

  • In different processes on the same machine

  • On different machines in the same network

  • In different environments (server, browser, edge)

No special configuration is needed - nodes communicate transparently across boundaries.

Next Steps

As you build with Happen, remember these key principles:

  1. Start Simple: Begin with a few nodes that have clear responsibilities

  2. Focus on Events: Design meaningful events that capture domain concepts

  3. Control Transitions: Use function returns to control your flow

  4. Observe Patterns: Let natural workflows emerge from event interactions

  5. Compose, Don't Complicate: Combine the basic primitives instead of adding complexity

  6. Embrace Causality: Use the causal event web to reason about your system

The power of Happen comes not from complex features or abstractions, but from how its minimal primitives combine to create emergent behaviors.

Ready to explore more? Continue to the Core Concepts section to deepen your understanding of nodes, events, and the Happen framework.

Last updated