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:
Nodes - Independent, autonomous components that process and respond to information
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:
Explicit Transitions: You control what happens next by returning either a value (to complete) or a function (to continue)
Shared Context: Information flows between steps through the context object
Natural Branching: Flow can branch based on conditions without complex routing rules
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:
Start Simple: Begin with a few nodes that have clear responsibilities
Focus on Events: Design meaningful events that capture domain concepts
Control Transitions: Use function returns to control your flow
Observe Patterns: Let natural workflows emerge from event interactions
Compose, Don't Complicate: Combine the basic primitives instead of adding complexity
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